Files
keyboard/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m

304 lines
8.7 KiB
Mathematica
Raw Normal View History

2026-01-26 16:53:41 +08:00
//
// KBAIHomeVC.m
// keyBoard
//
// Created by Mac on 2026/1/26.
//
#import "KBAIHomeVC.h"
#import "KBPersonaChatCell.h"
#import "KBPersonaModel.h"
2026-01-26 18:17:02 +08:00
#import "KBVoiceInputBar.h"
2026-01-26 16:53:41 +08:00
#import "AiVM.h"
#import <Masonry/Masonry.h>
2026-01-26 18:17:02 +08:00
@interface KBAIHomeVC () <UICollectionViewDelegate, UICollectionViewDataSource, KBVoiceInputBarDelegate>
2026-01-26 16:53:41 +08:00
///
@property (nonatomic, strong) UICollectionView *collectionView;
2026-01-26 18:17:02 +08:00
///
@property (nonatomic, strong) KBVoiceInputBar *voiceInputBar;
2026-01-26 16:53:41 +08:00
///
@property (nonatomic, strong) NSMutableArray<KBPersonaModel *> *personas;
///
@property (nonatomic, assign) NSInteger currentPage;
///
@property (nonatomic, assign) BOOL hasMore;
///
@property (nonatomic, assign) BOOL isLoading;
///
@property (nonatomic, assign) NSInteger currentIndex;
///
@property (nonatomic, strong) NSMutableSet<NSNumber *> *preloadedIndexes;
/// AiVM
@property (nonatomic, strong) AiVM *aiVM;
@end
@implementation KBAIHomeVC
#pragma mark - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.kb_navView.hidden = true;
//
self.personas = [NSMutableArray array];
self.currentPage = 1;
self.hasMore = YES;
self.isLoading = NO;
self.currentIndex = 0;
self.preloadedIndexes = [NSMutableSet set];
self.aiVM = [[AiVM alloc] init];
[self setupUI];
[self loadPersonas];
}
#pragma mark - 1
- (void)setupUI {
[self.view addSubview:self.collectionView];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
2026-01-26 18:17:02 +08:00
//
[self.view addSubview:self.voiceInputBar];
[self.voiceInputBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view);
make.bottom.equalTo(self.view);
make.height.mas_equalTo(150); //
}];
2026-01-26 16:53:41 +08:00
}
#pragma mark - 2
- (void)loadPersonas {
if (self.isLoading) {
return;
}
self.isLoading = YES;
__weak typeof(self) weakSelf = self;
[self.aiVM fetchPersonasWithPageNum:self.currentPage
pageSize:10
completion:^(KBPersonaPageModel * _Nullable pageModel, NSError * _Nullable error) {
weakSelf.isLoading = NO;
if (error) {
NSLog(@"加载人设列表失败:%@", error.localizedDescription);
// TODO:
return;
}
if (!pageModel || !pageModel.records) {
NSLog(@"人设列表数据为空");
return;
}
//
[weakSelf.personas addObjectsFromArray:pageModel.records];
weakSelf.hasMore = pageModel.hasMore;
// UI
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.collectionView reloadData];
// 3
if (weakSelf.currentPage == 1) {
[weakSelf preloadDataForIndexes:@[@0, @1, @2]];
}
});
NSLog(@"加载成功:当前 %ld 条,总共 %ld 条,还有更多:%@",
weakSelf.personas.count,
pageModel.total,
pageModel.hasMore ? @"是" : @"否");
}];
}
- (void)loadMorePersonas {
if (!self.hasMore || self.isLoading) {
return;
}
self.currentPage++;
[self loadPersonas];
}
#pragma mark - 3
- (void)preloadAdjacentCellsForIndex:(NSInteger)index {
if (index < 0 || index >= self.personas.count) {
return;
}
NSMutableArray *indexesToPreload = [NSMutableArray array];
//
if (index > 0) {
[indexesToPreload addObject:@(index - 1)];
}
//
[indexesToPreload addObject:@(index)];
//
if (index < self.personas.count - 1) {
[indexesToPreload addObject:@(index + 1)];
}
[self preloadDataForIndexes:indexesToPreload];
}
- (void)preloadDataForIndexes:(NSArray<NSNumber *> *)indexes {
for (NSNumber *indexNum in indexes) {
if ([self.preloadedIndexes containsObject:indexNum]) {
continue;
}
[self.preloadedIndexes addObject:indexNum];
NSInteger index = [indexNum integerValue];
if (index >= self.personas.count) {
continue;
}
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
KBPersonaChatCell *cell = (KBPersonaChatCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
if (cell) {
[cell preloadDataIfNeeded];
}
NSLog(@"预加载第 %ld 个人设", (long)index);
}
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.personas.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
KBPersonaChatCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"KBPersonaChatCell" forIndexPath:indexPath];
cell.persona = self.personas[indexPath.item];
2026-01-26 18:17:02 +08:00
//
[self.preloadedIndexes addObject:@(indexPath.item)];
//
[cell preloadDataIfNeeded];
2026-01-26 16:53:41 +08:00
return cell;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageHeight = scrollView.bounds.size.height;
CGFloat offsetY = scrollView.contentOffset.y;
NSInteger currentPage = offsetY / pageHeight;
// 30%
if (fmod(offsetY, pageHeight) > pageHeight * 0.3) {
[self preloadAdjacentCellsForIndex:currentPage + 1];
} else {
[self preloadAdjacentCellsForIndex:currentPage];
}
//
if (offsetY + scrollView.bounds.size.height >= scrollView.contentSize.height - pageHeight) {
[self loadMorePersonas];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
CGFloat pageHeight = scrollView.bounds.size.height;
NSInteger currentPage = scrollView.contentOffset.y / pageHeight;
self.currentIndex = currentPage;
if (currentPage < self.personas.count) {
NSLog(@"当前在第 %ld 个人设:%@", (long)currentPage, self.personas[currentPage].name);
}
}
#pragma mark - Lazy Load
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
layout.itemSize = [UIScreen mainScreen].bounds.size;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.pagingEnabled = YES;
_collectionView.showsVerticalScrollIndicator = NO;
_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView registerClass:[KBPersonaChatCell class] forCellWithReuseIdentifier:@"KBPersonaChatCell"];
if (@available(iOS 11.0, *)) {
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
return _collectionView;
}
2026-01-26 18:17:02 +08:00
- (KBVoiceInputBar *)voiceInputBar {
if (!_voiceInputBar) {
_voiceInputBar = [[KBVoiceInputBar alloc] init];
_voiceInputBar.delegate = self;
_voiceInputBar.statusText = @"按住按钮开始对话";
}
return _voiceInputBar;
}
#pragma mark - KBVoiceInputBarDelegate
- (void)voiceInputBarDidBeginRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 开始录音");
inputBar.statusText = @"正在聆听...";
// TODO:
// 1.
// 2.
// 3.
}
- (void)voiceInputBarDidEndRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 结束录音");
inputBar.statusText = @"正在识别...";
// TODO:
// 1.
// 2.
// 3.
}
- (void)voiceInputBarDidCancelRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 取消录音");
inputBar.statusText = @"已取消";
// TODO:
// 1.
// 2.
}
2026-01-26 16:53:41 +08:00
@end