diff --git a/YuMi.xcodeproj/project.pbxproj b/YuMi.xcodeproj/project.pbxproj index 6c39312..5118142 100644 --- a/YuMi.xcodeproj/project.pbxproj +++ b/YuMi.xcodeproj/project.pbxproj @@ -427,6 +427,7 @@ 4C0642912E98DC8700BAF413 /* EPMomentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0642902E98DC8700BAF413 /* EPMomentViewController.m */; }; 4C0642962E98F76F00BAF413 /* EPMomentAPIHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0642942E98F76F00BAF413 /* EPMomentAPIHelper.m */; }; 4C0642992E98F77900BAF413 /* EPMomentListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0642982E98F77900BAF413 /* EPMomentListView.m */; }; + 4C06429C2E99120600BAF413 /* EPMomentPublishViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C06429B2E99120600BAF413 /* EPMomentPublishViewController.m */; }; 4C0A5B842E02675300955219 /* MedalsCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B832E02675300955219 /* MedalsCollectionViewCell.m */; }; 4C0A5B872E02BB1100955219 /* MedalsLevelIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B862E02BB1100955219 /* MedalsLevelIndicatorView.m */; }; 4C0A5B8A2E02BC3900955219 /* MedalsDetailView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B892E02BC3900955219 /* MedalsDetailView.m */; }; @@ -2481,6 +2482,8 @@ 4C0642942E98F76F00BAF413 /* EPMomentAPIHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMomentAPIHelper.m; sourceTree = ""; }; 4C0642972E98F77900BAF413 /* EPMomentListView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMomentListView.h; sourceTree = ""; }; 4C0642982E98F77900BAF413 /* EPMomentListView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMomentListView.m; sourceTree = ""; }; + 4C06429A2E99120600BAF413 /* EPMomentPublishViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMomentPublishViewController.h; sourceTree = ""; }; + 4C06429B2E99120600BAF413 /* EPMomentPublishViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMomentPublishViewController.m; sourceTree = ""; }; 4C0A5B822E02675300955219 /* MedalsCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MedalsCollectionViewCell.h; sourceTree = ""; }; 4C0A5B832E02675300955219 /* MedalsCollectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MedalsCollectionViewCell.m; sourceTree = ""; }; 4C0A5B852E02BB1100955219 /* MedalsLevelIndicatorView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MedalsLevelIndicatorView.h; sourceTree = ""; }; @@ -6529,6 +6532,8 @@ 4C0642782E97BD6D00BAF413 /* Controllers */ = { isa = PBXGroup; children = ( + 4C06429A2E99120600BAF413 /* EPMomentPublishViewController.h */, + 4C06429B2E99120600BAF413 /* EPMomentPublishViewController.m */, 4C06428F2E98DC8700BAF413 /* EPMomentViewController.h */, 4C0642902E98DC8700BAF413 /* EPMomentViewController.m */, ); @@ -13008,6 +13013,7 @@ 2305F3472AD94E9D00AD403C /* XPMaskManagerCell.m in Sources */, E852D74428633A08001465ED /* MonentsCommentModel.m in Sources */, E8C1CD6D27D8938C00376F83 /* XPRoomFaceTitleCollectionViewCell.m in Sources */, + 4C06429C2E99120600BAF413 /* EPMomentPublishViewController.m in Sources */, 548E01C62C3F78360071C83D /* FeedBackViewController.m in Sources */, E8C1CD7627D8AE3D00376F83 /* XPRoomFacePresenter.m in Sources */, E85E7B362A4EB0D300B6D00A /* XPClanRoomCollectionViewCell.m in Sources */, diff --git a/YuMi/E-P/NewMoments/Controllers/EPMomentPublishViewController.h b/YuMi/E-P/NewMoments/Controllers/EPMomentPublishViewController.h new file mode 100644 index 0000000..5917672 --- /dev/null +++ b/YuMi/E-P/NewMoments/Controllers/EPMomentPublishViewController.h @@ -0,0 +1,19 @@ +// +// EPMomentPublishViewController.h +// YuMi +// +// Created by AI on 2025-10-10. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// EP 版:图文发布页面 +@interface EPMomentPublishViewController : UIViewController + +@end + +NS_ASSUME_NONNULL_END + + diff --git a/YuMi/E-P/NewMoments/Controllers/EPMomentPublishViewController.m b/YuMi/E-P/NewMoments/Controllers/EPMomentPublishViewController.m new file mode 100644 index 0000000..89e14bc --- /dev/null +++ b/YuMi/E-P/NewMoments/Controllers/EPMomentPublishViewController.m @@ -0,0 +1,206 @@ +// +// EPMomentPublishViewController.m +// YuMi +// +// Created by AI on 2025-10-10. +// + +#import "EPMomentPublishViewController.h" +#import +#import +#import "DJDKMIMOMColor.h" +#import "SZTextView.h" + +@interface EPMomentPublishViewController () + +@property (nonatomic, strong) UIView *navView; +@property (nonatomic, strong) UIButton *backButton; +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) UIButton *publishButton; + +@property (nonatomic, strong) UIView *contentView; +@property (nonatomic, strong) SZTextView *textView; +@property (nonatomic, strong) UILabel *limitLabel; +@property (nonatomic, strong) UIView *lineView; +@property (nonatomic, strong) UICollectionView *collectionView; +@property (nonatomic, strong) NSMutableArray *images; +@property (nonatomic, strong) NSMutableArray *selectedAssets; // TZImagePicker 已选资源 + +@end + +@implementation EPMomentPublishViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor colorWithRed:0x0C/255.0 green:0x05/255.0 blue:0x27/255.0 alpha:1.0]; + [self setupUI]; +} + +- (void)setupUI { + [self.view addSubview:self.navView]; + [self.view addSubview:self.contentView]; + [self.navView addSubview:self.backButton]; + [self.navView addSubview:self.titleLabel]; + // 发布按钮移到底部 + [self.contentView addSubview:self.textView]; + [self.contentView addSubview:self.limitLabel]; + [self.contentView addSubview:self.lineView]; + [self.contentView addSubview:self.collectionView]; + [self.contentView addSubview:self.publishButton]; + + [self.navView mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.trailing.top.equalTo(self.view); + make.height.mas_equalTo(kNavigationHeight); + }]; + [self.backButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.equalTo(self.view).offset(10); + make.top.mas_equalTo(statusbarHeight); + make.size.mas_equalTo(CGSizeMake(44, 44)); + }]; + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.equalTo(self.navView); + make.centerY.equalTo(self.backButton); + }]; + // 发布按钮约束移到底部 + [self.contentView mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.trailing.equalTo(self.view); + make.top.equalTo(self.navView.mas_bottom); + make.bottom.equalTo(self.view); + }]; + [self.textView mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.trailing.equalTo(self.contentView).inset(15); + make.top.equalTo(self.contentView).offset(10); + make.height.mas_equalTo(150); + }]; + [self.limitLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.textView.mas_bottom).offset(5); + make.trailing.equalTo(self.textView); + }]; + [self.lineView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.limitLabel.mas_bottom).offset(10); + make.leading.trailing.equalTo(self.textView); + make.height.mas_equalTo(1); + }]; + [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.trailing.equalTo(self.contentView).inset(15); + make.top.equalTo(self.lineView.mas_bottom).offset(10); + make.height.mas_equalTo(110); + }]; + + // 底部发布按钮 + [self.publishButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.trailing.equalTo(self.view).inset(20); + make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-20); + make.height.mas_equalTo(50); + }]; +} + +#pragma mark - Actions + +- (void)onBack { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)onPublish { + // TODO: 挂接实际发布逻辑 + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - UICollectionView + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.images.count + 1; // 最后一个是添加按钮 +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ep.publish.cell" forIndexPath:indexPath]; + cell.contentView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.06]; + cell.contentView.layer.cornerRadius = 12; + // 清空复用子视图,避免加号被覆盖 + for (UIView *sub in cell.contentView.subviews) { [sub removeFromSuperview]; } + BOOL showAdd = (self.images.count < 9) && (indexPath.item == self.images.count); + if (showAdd) { + UIImageView *iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mine_user_info_album_add"]]; + [cell.contentView addSubview:iv]; + [iv mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(cell.contentView); make.size.mas_equalTo(CGSizeMake(24, 24)); }]; + } else { + UIImageView *iv = [[UIImageView alloc] init]; + iv.contentMode = UIViewContentModeScaleAspectFill; + iv.layer.masksToBounds = YES; + [cell.contentView addSubview:iv]; + [iv mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(cell.contentView); }]; + NSInteger idx = MIN(indexPath.item, (NSInteger)self.images.count - 1); + if (idx >= 0 && idx < self.images.count) iv.image = self.images[idx]; + } + return cell; +} + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.item == self.images.count) { + TZImagePickerController *picker = [[TZImagePickerController alloc] initWithMaxImagesCount:9 delegate:self]; + picker.allowPickingVideo = NO; + picker.allowTakeVideo = NO; + picker.selectedAssets = self.selectedAssets; // 预选 + picker.maxImagesCount = 9; // 总上限 + [self presentViewController:picker animated:YES completion:nil]; + } +} + +#pragma mark - TZImagePickerControllerDelegate +- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto infos:(NSArray *)infos { + // 合并选择:在已有基础上追加,最多 9 张 + for (NSInteger i = 0; i < assets.count; i++) { + id asset = assets[i]; + UIImage *img = [photos xpSafeObjectAtIndex:i] ?: photos[i]; + if (![self.selectedAssets containsObject:asset] && self.images.count < 9) { + [self.selectedAssets addObject:asset]; + [self.images addObject:img]; + } + } + [self.collectionView reloadData]; +} + +#pragma mark - UITextViewDelegate +- (void)textViewDidChange:(UITextView *)textView { + if (textView.text.length > 500) { + textView.text = [textView.text substringToIndex:500]; + } + self.limitLabel.text = [NSString stringWithFormat:@"%lu/500", (unsigned long)textView.text.length]; +} + +#pragma mark - Lazy + +- (UIView *)navView { if (!_navView) { _navView = [UIView new]; _navView.backgroundColor = [UIColor clearColor]; } return _navView; } +- (UIButton *)backButton { if (!_backButton) { _backButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_backButton setImage:[UIImage imageNamed:@"common_nav_back"] forState:UIControlStateNormal]; [_backButton addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside]; } return _backButton; } +- (UILabel *)titleLabel { if (!_titleLabel) { _titleLabel = [UILabel new]; _titleLabel.text = @"图文发布"; _titleLabel.textColor = [DJDKMIMOMColor mainTextColor]; _titleLabel.font = [UIFont systemFontOfSize:17]; } return _titleLabel; } +- (UIButton *)publishButton { + if (!_publishButton) { + _publishButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_publishButton setTitle:@"发布" forState:UIControlStateNormal]; + [_publishButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + _publishButton.titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium]; + _publishButton.layer.cornerRadius = 25; + _publishButton.layer.masksToBounds = YES; + // 渐变背景:从浅紫到深紫 + CAGradientLayer *gradient = [CAGradientLayer layer]; + gradient.colors = @[(__bridge id)[UIColor colorWithRed:0.6 green:0.3 blue:0.8 alpha:1.0].CGColor, + (__bridge id)[UIColor colorWithRed:0.3 green:0.1 blue:0.5 alpha:1.0].CGColor]; + gradient.startPoint = CGPointMake(0, 0); + gradient.endPoint = CGPointMake(1, 0); + gradient.frame = CGRectMake(0, 0, 1, 1); + [_publishButton.layer insertSublayer:gradient atIndex:0]; + [_publishButton addTarget:self action:@selector(onPublish) forControlEvents:UIControlEventTouchUpInside]; + } + return _publishButton; +} +- (UIView *)contentView { if (!_contentView) { _contentView = [UIView new]; _contentView.backgroundColor = [UIColor clearColor]; } return _contentView; } +- (SZTextView *)textView { if (!_textView) { _textView = [SZTextView new]; _textView.placeholder = @"Enter Content"; _textView.textColor = [DJDKMIMOMColor mainTextColor]; _textView.placeholderTextColor = [DJDKMIMOMColor secondTextColor]; _textView.font = [UIFont systemFontOfSize:15]; _textView.delegate = self; } return _textView; } +- (UILabel *)limitLabel { if (!_limitLabel) { _limitLabel = [UILabel new]; _limitLabel.text = @"0/500"; _limitLabel.textColor = [DJDKMIMOMColor mainTextColor]; _limitLabel.font = [UIFont systemFontOfSize:12]; } return _limitLabel; } +- (UIView *)lineView { if (!_lineView) { _lineView = [UIView new]; _lineView.backgroundColor = [DJDKMIMOMColor dividerColor]; } return _lineView; } +- (UICollectionView *)collectionView { if (!_collectionView) { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.minimumLineSpacing = 10; layout.minimumInteritemSpacing = 10; CGFloat itemW = (KScreenWidth - 15*2 - 10*2)/3.0; layout.itemSize = CGSizeMake(itemW, itemW); _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; _collectionView.delegate = self; _collectionView.dataSource = self; _collectionView.backgroundColor = [UIColor clearColor]; [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"ep.publish.cell"]; } return _collectionView; } +- (NSMutableArray *)images { if (!_images) { _images = [NSMutableArray array]; } return _images; } +- (NSMutableArray *)selectedAssets { if (!_selectedAssets) { _selectedAssets = [NSMutableArray array]; } return _selectedAssets; } + +@end + + diff --git a/YuMi/E-P/NewMoments/Controllers/EPMomentViewController.m b/YuMi/E-P/NewMoments/Controllers/EPMomentViewController.m index 7b44823..ce6643d 100644 --- a/YuMi/E-P/NewMoments/Controllers/EPMomentViewController.m +++ b/YuMi/E-P/NewMoments/Controllers/EPMomentViewController.m @@ -11,6 +11,7 @@ #import #import "EPMomentCell.h" #import "EPMomentListView.h" +#import "EPMomentPublishViewController.h" @interface EPMomentViewController () @@ -88,8 +89,9 @@ - (void)onPublishButtonTapped { NSLog(@"[EPMomentViewController] 发布按钮点击"); - // TODO: 跳转到发布页面 - [self showAlertWithMessage:@"发布功能开发中"]; + EPMomentPublishViewController *vc = [[EPMomentPublishViewController alloc] init]; + vc.modalPresentationStyle = UIModalPresentationFullScreen; + [self.navigationController presentViewController:vc animated:YES completion:nil]; } - (void)showAlertWithMessage:(NSString *)message { diff --git a/YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.h b/YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.h index 7125fdc..0b882e2 100644 --- a/YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.h +++ b/YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.h @@ -21,13 +21,10 @@ typedef NS_ENUM(NSInteger, EPMomentListSourceType) { /// 统一封装 Moments 列表 API @interface EPMomentAPIHelper : BaseMvpPresenter -/// 拉取动态列表(默认 types:"0,2" 图片+文字) -/// page 从 0 开始,pageSize > 0 -/// completion 返回 data 数组(字典数组) 或错误 -- (void)fetchMomentsWithType:(EPMomentListSourceType)sourceType - page:(NSInteger)page - pageSize:(NSInteger)pageSize - completion:(void (^)(NSArray * _Nullable list, NSInteger code, NSString * _Nullable msg))completion; +/// 拉取最新动态列表(默认 types:"0,2" 图片+文字) +- (void)fetchLatestMomentsWithNextID:(NSString *)nextID + completion:(void (^)(NSArray * _Nullable list, NSString *nextMomentID))completion + failure:(void(^)(NSInteger code, NSString * _Nullable msg))failure; @end diff --git a/YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.m b/YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.m index 3976008..07082f8 100644 --- a/YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.m +++ b/YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.m @@ -13,18 +13,6 @@ @implementation EPMomentAPIHelper - -- (void)fetchMomentsWithType:(EPMomentListSourceType)sourceType - page:(NSInteger)page - pageSize:(NSInteger)pageSize - completion:(void (^)(NSArray * _Nullable list, NSInteger code, NSString * _Nullable msg))completion { - // 兼容后端从 1 开始分页:若收到 0 则转成 1 - NSInteger requestPage = page <= 0 ? 1 : page; - NSString *pageStr = [NSString stringWithFormat:@"%ld", (long)requestPage]; - NSString *pageSizeStr = [NSString stringWithFormat:@"%ld", (long)pageSize]; - NSString *types = @"0,2"; // 图片+文字 - - if (sourceType == EPMomentListSourceTypeRecommend) { // [Api momentsRecommendList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { // if (code == 200 && data.data) { // NSArray *array = [MomentsInfoModel modelsWithArray:data.data]; @@ -33,24 +21,20 @@ // if (completion) completion(@[], code, msg); // } // } page:pageStr pageSize:pageSizeStr types:types]; - [Api momentsLatestList:[self createHttpCompletion:^(BaseModel * _Nonnull data) { - MomentsListInfoModel *listInfo = [MomentsListInfoModel modelWithDictionary:data.data]; - if (completion) completion(listInfo.dynamicList ?: @[], 200, @"success"); - } fail:^(NSInteger code, NSString * _Nullable msg) { - if (completion) completion(@[], code, msg); - }] dynamicId:@"" pageSize:pageSizeStr types:types]; - - } else { - // 预留:我的动态列表(暂时复用推荐接口,后续替换为真正的“我的动态”API) - [Api momentsRecommendList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { - if (code == 200 && data.data) { - NSArray *array = [MomentsInfoModel modelsWithArray:data.data]; - if (completion) completion(array ?: @[], 200, @"success"); - } else { - if (completion) completion(@[], code, msg); - } - } page:pageStr pageSize:pageSizeStr types:types]; - } + +- (void)fetchLatestMomentsWithNextID:(NSString *)nextID + completion:(void (^)(NSArray * _Nullable list, NSString *nextMomentID))completion + failure:(void(^)(NSInteger code, NSString * _Nullable msg))failure { + NSString *pageSizeStr = @"20"; + NSString *types = @"0,2"; // 图片+文字 + + [Api momentsLatestList:[self createHttpCompletion:^(BaseModel * _Nonnull data) { + MomentsListInfoModel *listInfo = [MomentsListInfoModel modelWithDictionary:data.data]; + if (completion) completion(listInfo.dynamicList ?: @[], + listInfo.nextDynamicId); + } fail:^(NSInteger code, NSString * _Nullable msg) { + if (failure) failure(code, msg); + }] dynamicId:nextID pageSize:pageSizeStr types:types]; } @end diff --git a/YuMi/E-P/NewMoments/Views/EPMomentCell.m b/YuMi/E-P/NewMoments/Views/EPMomentCell.m index feab046..f02b4df 100644 --- a/YuMi/E-P/NewMoments/Views/EPMomentCell.m +++ b/YuMi/E-P/NewMoments/Views/EPMomentCell.m @@ -10,7 +10,7 @@ #import "MomentsInfoModel.h" #import "AccountInfoStorage.h" #import "Api+Moments.h" -#import +#import "NetImageView.h" @interface EPMomentCell () @@ -19,8 +19,8 @@ /// 卡片容器 @property (nonatomic, strong) UIView *cardView; -/// 头像 -@property (nonatomic, strong) UIImageView *avatarImageView; +/// 头像(网络) +@property (nonatomic, strong) NetImageView *avatarImageView; /// 用户名 @property (nonatomic, strong) UILabel *nameLabel; @@ -31,8 +31,9 @@ /// 内容标签 @property (nonatomic, strong) UILabel *contentLabel; -/// 图片容器(可选) +/// 图片容器(九宫格) @property (nonatomic, strong) UIView *imagesContainer; +@property (nonatomic, strong) NSMutableArray *imageViews; /// 底部操作栏 @property (nonatomic, strong) UIView *actionBar; @@ -43,8 +44,7 @@ /// 评论按钮 @property (nonatomic, strong) UIButton *commentButton; -/// 分享按钮 -@property (nonatomic, strong) UIButton *shareButton; +// 分享按钮已移除 /// 当前数据模型 @property (nonatomic, strong) MomentsInfoModel *currentModel; @@ -108,11 +108,19 @@ make.top.equalTo(self.avatarImageView.mas_bottom).offset(12); }]; + // 图片九宫格 + [self.cardView addSubview:self.imagesContainer]; + [self.imagesContainer mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.cardView).offset(15); + make.right.equalTo(self.cardView).offset(-15); + make.top.equalTo(self.contentLabel.mas_bottom).offset(12); + }]; + // 底部操作栏 [self.cardView addSubview:self.actionBar]; [self.actionBar mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.cardView); - make.top.equalTo(self.contentLabel.mas_bottom).offset(15); + make.top.equalTo(self.imagesContainer.mas_bottom).offset(12); make.height.mas_equalTo(50); make.bottom.equalTo(self.cardView).offset(-8); }]; @@ -133,13 +141,7 @@ make.width.mas_greaterThanOrEqualTo(60); }]; - // 分享按钮 - [self.actionBar addSubview:self.shareButton]; - [self.shareButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.right.equalTo(self.actionBar).offset(-15); - make.centerY.equalTo(self.actionBar); - make.width.mas_greaterThanOrEqualTo(60); - }]; + // 右侧占位(去掉分享按钮后,右边保持留白) } // MARK: - Public Methods @@ -156,17 +158,74 @@ // 配置内容 self.contentLabel.text = model.content ?: @""; - // 配置点赞数 - [self.likeButton setTitle:[NSString stringWithFormat:@"👍 %ld", (long)model.likeCount] forState:UIControlStateNormal]; + // 配置图片九宫格 + [self renderImages:model.dynamicResList]; + + // 配置点赞/评论数(安全整型,避免负数和溢出) + NSInteger likeCnt = MAX(0, model.likeCount.integerValue); + NSInteger cmtCnt = MAX(0, model.commentCount.integerValue); + [self.likeButton setTitle:[NSString stringWithFormat:@"👍 %ld", (long)likeCnt] forState:UIControlStateNormal]; + [self.commentButton setTitle:[NSString stringWithFormat:@"💬 %ld", (long)cmtCnt] forState:UIControlStateNormal]; - // 配置评论数 - [self.commentButton setTitle:[NSString stringWithFormat:@"💬 %ld", (long)model.commentCount] forState:UIControlStateNormal]; - - // 配置分享 - [self.shareButton setTitle:@"🔗 分享" forState:UIControlStateNormal]; - - // TODO: 加载头像图片 - // [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:model.avatar]]; + self.avatarImageView.imageUrl = model.avatar; +} + +// MARK: - Images Grid + +- (void)renderImages:(NSArray *)resList { + // 清理旧视图 + for (UIView *iv in self.imageViews) { [iv removeFromSuperview]; } + [self.imageViews removeAllObjects]; + if (resList.count == 0) { + [self.imagesContainer mas_remakeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.cardView).offset(15); + make.right.equalTo(self.cardView).offset(-15); + make.top.equalTo(self.contentLabel.mas_bottom).offset(0); + make.height.mas_equalTo(0); + }]; + return; + } + NSInteger columns = 3; + CGFloat spacing = 6.0; + CGFloat totalWidth = [UIScreen mainScreen].bounds.size.width - 30 - 30; // 左右各 15 内边距,再减卡片左右 15 + CGFloat itemW = floor((totalWidth - spacing * (columns - 1)) / columns); + + for (NSInteger i = 0; i < resList.count && i < 9; i++) { + NetImageConfig *config = [[NetImageConfig alloc] init]; + config.placeHolder = [UIImageConstant defaultBannerPlaceholder]; + NetImageView *iv = [[NetImageView alloc] initWithConfig:config]; + iv.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0]; + iv.layer.cornerRadius = 6; + iv.layer.masksToBounds = YES; + iv.contentMode = UIViewContentModeScaleAspectFill; + [self.imagesContainer addSubview:iv]; + [self.imageViews addObject:iv]; + NSInteger row = i / columns; + NSInteger col = i % columns; + [iv mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.imagesContainer).offset((itemW + spacing) * col); + make.top.equalTo(self.imagesContainer).offset((itemW + spacing) * row); + make.size.mas_equalTo(CGSizeMake(itemW, itemW)); + }]; + // 绑定网络图片 + NSString *url = nil; + id item = resList[i]; + if ([item isKindOfClass:[NSDictionary class]]) { + url = [item valueForKey:@"resUrl"] ?: [item valueForKey:@"url"]; + } else if ([item respondsToSelector:@selector(resUrl)]) { + url = [item valueForKey:@"resUrl"]; + } + iv.imageUrl = url; + } + + NSInteger rows = ((MIN(resList.count, 9) - 1) / columns) + 1; + CGFloat height = rows * itemW + (rows - 1) * spacing; + [self.imagesContainer mas_remakeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.cardView).offset(15); + make.right.equalTo(self.cardView).offset(-15); + make.top.equalTo(self.contentLabel.mas_bottom).offset(12); + make.height.mas_equalTo(height); + }]; } /// 格式化时间戳为相对时间 @@ -249,7 +308,10 @@ - (UIImageView *)avatarImageView { if (!_avatarImageView) { - _avatarImageView = [[UIImageView alloc] init]; + NetImageConfig *config = [[NetImageConfig alloc] init]; + config.imageType = ImageTypeUserIcon; + config.placeHolder = [UIImageConstant defaultAvatarPlaceholder]; + _avatarImageView = [[NetImageView alloc] initWithConfig:config]; _avatarImageView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0]; _avatarImageView.layer.cornerRadius = 8; // 圆角矩形,不是圆形! _avatarImageView.layer.masksToBounds = YES; @@ -312,11 +374,7 @@ } - (UIButton *)shareButton { - if (!_shareButton) { - _shareButton = [self createActionButtonWithTitle:@"🔗 分享"]; - [_shareButton addTarget:self action:@selector(onShareButtonTapped) forControlEvents:UIControlEventTouchUpInside]; - } - return _shareButton; + return nil; } - (UIButton *)createActionButtonWithTitle:(NSString *)title { @@ -327,4 +385,19 @@ return button; } +- (UIView *)imagesContainer { + if (!_imagesContainer) { + _imagesContainer = [[UIView alloc] init]; + _imagesContainer.backgroundColor = [UIColor clearColor]; + } + return _imagesContainer; +} + +- (NSMutableArray *)imageViews { + if (!_imageViews) { + _imageViews = [NSMutableArray array]; + } + return _imageViews; +} + @end diff --git a/YuMi/E-P/NewMoments/Views/EPMomentListView.m b/YuMi/E-P/NewMoments/Views/EPMomentListView.m index 0dc385a..38a067a 100644 --- a/YuMi/E-P/NewMoments/Views/EPMomentListView.m +++ b/YuMi/E-P/NewMoments/Views/EPMomentListView.m @@ -8,6 +8,7 @@ #import #import "EPMomentListView.h" #import "EPMomentCell.h" +#import @interface EPMomentListView () @@ -16,9 +17,8 @@ @property (nonatomic, strong) UIRefreshControl *refreshControl; @property (nonatomic, strong) NSMutableArray *mutableRawList; @property (nonatomic, strong) EPMomentAPIHelper *api; -@property (nonatomic, assign) NSInteger currentPage; @property (nonatomic, assign) BOOL isLoading; - +@property (nonatomic, copy) NSString *nextID; @end @implementation EPMomentListView @@ -29,7 +29,6 @@ self.backgroundColor = [UIColor clearColor]; _api = [[EPMomentAPIHelper alloc] init]; _mutableRawList = [NSMutableArray array]; - _currentPage = 0; // 若后端从1开始,这里会在首次请求时自增到1 _sourceType = EPMomentListSourceTypeRecommend; [self addSubview:self.tableView]; @@ -45,9 +44,10 @@ } - (void)reloadFirstPage { - self.currentPage = 0; + self.nextID = @""; [self.mutableRawList removeAllObjects]; [self.tableView reloadData]; + [self.tableView.mj_footer resetNoMoreData]; [self requestNextPage]; } @@ -55,25 +55,41 @@ if (self.isLoading) return; self.isLoading = YES; - NSLog(@"[EPMomentListView] 请求页码: %ld", (long)self.currentPage); @kWeakify(self); - [self.api fetchMomentsWithType:self.sourceType page:self.currentPage pageSize:20 completion:^(NSArray * _Nullable list, NSInteger code, NSString * _Nullable msg) { + [self.api fetchLatestMomentsWithNextID:self.nextID + completion:^(NSArray * _Nullable list, NSString * _Nonnull nextMomentID) { @kStrongify(self); - self.isLoading = NO; - [self.refreshControl endRefreshing]; - NSLog(@"[EPMomentListView] 返回 code=%ld, count=%lu", (long)code, (unsigned long)list.count); - if (code == 200 && list.count > 0) { + [self endLoading]; + if (list.count > 0) { + self.nextID = nextMomentID; [self.mutableRawList addObjectsFromArray:list]; - self.currentPage++; [self.tableView reloadData]; - } else if (code == 200 && list.count == 0 && self.currentPage == 0) { - // 如果第一页就为空,尝试从1开始(兼容某些后端从1计数) - self.currentPage = 1; - [self requestNextPage]; + if (nextMomentID.length > 0) { + [self.tableView.mj_footer endRefreshing]; + } else { + [self.tableView.mj_footer endRefreshingWithNoMoreData]; + } + } else { + // TODO: 后续补充空数据页面 + if (self.nextID.length == 0) { + [self.tableView.mj_footer endRefreshingWithNoMoreData]; + } else { + [self.tableView.mj_footer endRefreshing]; + } } + } failure:^(NSInteger code, NSString * _Nullable msg) { + @kStrongify(self); + [self endLoading]; + // TODO: 完全没有数据情况下,后续补充数据异常页面 + [self.tableView.mj_footer endRefreshing]; }]; } +- (void)endLoading { + self.isLoading = NO; + [self.refreshControl endRefreshing]; +} + #pragma mark - UITableView - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { @@ -123,9 +139,24 @@ _tableView.estimatedRowHeight = 200; _tableView.rowHeight = UITableViewAutomaticDimension; _tableView.showsVerticalScrollIndicator = NO; - _tableView.contentInset = UIEdgeInsetsMake(10, 0, 10, 0); + // 底部留出更高空间,避免被悬浮 TabBar 遮挡 + _tableView.contentInset = UIEdgeInsetsMake(10, 0, 120, 0); + _tableView.scrollIndicatorInsets = UIEdgeInsetsMake(10, 0, 120, 0); [_tableView registerClass:[EPMomentCell class] forCellReuseIdentifier:@"NewMomentCell"]; _tableView.refreshControl = self.refreshControl; + + // MJRefresh Footer - 加载更多 + __weak typeof(self) weakSelf = self; + _tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ + __strong typeof(weakSelf) self = weakSelf; + if (!self.isLoading && self.nextID.length > 0) { + [self requestNextPage]; + } else if (self.nextID.length == 0) { + [self.tableView.mj_footer endRefreshingWithNoMoreData]; + } else { + [self.tableView.mj_footer endRefreshing]; + } + }]; } return _tableView; }