// // NewMomentCell.m // YuMi // // Created by AI on 2025-10-09. // Copyright © 2025 YuMi. All rights reserved. // #import "EPMomentCell.h" #import "MomentsInfoModel.h" #import "AccountInfoStorage.h" #import "NetImageView.h" #import "EPEmotionColorStorage.h" #import "SDPhotoBrowser.h" #import "YuMi-Swift.h" // Swift 互操作 @interface EPMomentCell () // MARK: - UI Components /// 卡片容器 @property (nonatomic, strong) UIView *cardView; /// 彩色背景层(毛玻璃下方) @property (nonatomic, strong) UIView *colorBackgroundView; /// 毛玻璃效果视图 @property (nonatomic, strong) UIVisualEffectView *blurEffectView; /// 头像(网络) @property (nonatomic, strong) NetImageView *avatarImageView; /// 用户名 @property (nonatomic, strong) UILabel *nameLabel; /// 时间标签 @property (nonatomic, strong) UILabel *timeLabel; /// 内容标签 @property (nonatomic, strong) UILabel *contentLabel; /// 图片容器(九宫格) @property (nonatomic, strong) UIView *imagesContainer; @property (nonatomic, strong) NSMutableArray *imageViews; /// 底部操作栏 @property (nonatomic, strong) UIView *actionBar; /// 点赞按钮 @property (nonatomic, strong) UIButton *likeButton; /// 评论按钮 @property (nonatomic, strong) UIButton *commentButton; // 分享按钮已移除 /// 当前数据模型 @property (nonatomic, strong) MomentsInfoModel *currentModel; /// API Helper (Swift 版本) @property (nonatomic, strong) EPMomentAPISwiftHelper *apiHelper; @end @implementation EPMomentCell // MARK: - Lifecycle - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { self.selectionStyle = UITableViewCellSelectionStyleNone; self.backgroundColor = [UIColor clearColor]; [self setupUI]; } return self; } // MARK: - Setup UI - (void)setupUI { // 卡片容器(圆角矩形 + 阴影) [self.contentView addSubview:self.cardView]; [self.cardView mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.trailing.equalTo(self.contentView).inset(15); make.top.equalTo(self.contentView).offset(8); make.bottom.equalTo(self.contentView).offset(-8).priority(UILayoutPriorityRequired - 1); }]; // 彩色背景层(最底层) [self.cardView addSubview:self.colorBackgroundView]; [self.colorBackgroundView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.cardView); }]; // 毛玻璃效果视图(在彩色背景层之上) [self.cardView addSubview:self.blurEffectView]; [self.blurEffectView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.cardView); }]; // 头像( [self.blurEffectView.contentView addSubview:self.avatarImageView]; [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(self.cardView).offset(15); make.top.equalTo(self.cardView).offset(15); make.size.mas_equalTo(CGSizeMake(40, 40)); }]; // 用户名 [self.blurEffectView.contentView addSubview:self.nameLabel]; [self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(self.avatarImageView.mas_trailing).offset(10); make.top.equalTo(self.avatarImageView); make.trailing.equalTo(self.cardView).offset(-15); }]; // 时间 [self.blurEffectView.contentView addSubview:self.timeLabel]; [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(self.nameLabel); make.bottom.equalTo(self.avatarImageView); make.trailing.equalTo(self.cardView).offset(-15); }]; // 内容 [self.blurEffectView.contentView addSubview:self.contentLabel]; [self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.trailing.equalTo(self.cardView).inset(15); make.top.equalTo(self.avatarImageView.mas_bottom).offset(12); }]; // 图片九宫格 [self.blurEffectView.contentView addSubview:self.imagesContainer]; [self.imagesContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.trailing.equalTo(self.cardView).inset(15); make.top.equalTo(self.contentLabel.mas_bottom).offset(12); make.height.mas_equalTo(0); // 初始高度为0,renderImages 时会 remakeConstraints }]; // 底部操作栏 [self.blurEffectView.contentView addSubview:self.actionBar]; [self.actionBar mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.trailing.equalTo(self.cardView); make.top.equalTo(self.imagesContainer.mas_bottom).offset(12); make.height.mas_equalTo(50); // 设置较高优先级,确保底部约束生效 make.bottom.equalTo(self.cardView).offset(-8).priority(UILayoutPriorityRequired - 2); }]; // 点赞按钮(居左显示,评论功能已隐藏) [self.actionBar addSubview:self.likeButton]; [self.likeButton mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(self.actionBar); make.centerY.equalTo(self.actionBar); make.width.mas_greaterThanOrEqualTo(80); }]; } // MARK: - Public Methods - (void)configureWithModel:(MomentsInfoModel *)model { self.currentModel = model; // 配置用户名 self.nameLabel.text = model.nick ?: YMLocalizedString(@"user.anonymous"); // 配置时间(将时间戳转换为 MM/dd 格式) self.timeLabel.text = [self formatTimestampToDate:model.publishTime]; // 配置内容 self.contentLabel.text = model.content ?: @""; // 配置图片九宫格 [self renderImages:model.dynamicResList]; // 配置点赞按钮状态和数字 NSInteger likeCnt = MAX(0, model.likeCount.integerValue); self.likeButton.selected = model.isLike; [self.likeButton setTitle:[NSString stringWithFormat:@" %ld", (long)likeCnt] forState:UIControlStateNormal]; [self.likeButton setTitle:[NSString stringWithFormat:@" %ld", (long)likeCnt] forState:UIControlStateSelected]; self.avatarImageView.imageUrl = model.avatar; // 配置情绪颜色 border 和 shadow [self applyEmotionColorEffect:model.emotionColor]; // 确保布局完成后 cell 高度正确 [self setNeedsLayout]; } /// 应用情绪颜色视觉效果(Background + Shadow) - (void)applyEmotionColorEffect:(NSString *)emotionColorHex { // 获取颜色(已在列表加载时处理,这里直接使用) if (!emotionColorHex) { NSLog(@"[EPMomentCell] 警告:emotionColorHex 为 nil"); return; } UIColor *color = [self colorFromHex:emotionColorHex]; // 移除边框 self.cardView.layer.borderWidth = 0; // 设置彩色背景(50% 透明度,在毛玻璃下方) self.colorBackgroundView.backgroundColor = [color colorWithAlphaComponent:0.5]; // 设置 shadow(使用情绪颜色) self.cardView.layer.shadowColor = color.CGColor; self.cardView.layer.shadowOffset = CGSizeMake(0, 2); self.cardView.layer.shadowOpacity = 0.5; self.cardView.layer.shadowRadius = 16.0; } /// Hex 转 UIColor - (UIColor *)colorFromHex:(NSString *)hexString { unsigned rgbValue = 0; NSScanner *scanner = [NSScanner scannerWithString:hexString]; [scanner setScanLocation:1]; // 跳过 # [scanner scanHexInt:&rgbValue]; return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0]; } // 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.leading.trailing.equalTo(self.cardView).inset(15); make.top.equalTo(self.contentLabel.mas_bottom).offset(0); make.height.mas_equalTo(0); }]; // 强制触发布局更新 [self.contentView setNeedsLayout]; [self.contentView layoutIfNeeded]; 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; iv.userInteractionEnabled = YES; iv.tag = i; // 用于识别点击的图片索引 // 添加点击手势 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onImageTapped:)]; [iv addGestureRecognizer:tap]; [self.imagesContainer addSubview:iv]; [self.imageViews addObject:iv]; NSInteger row = i / columns; NSInteger col = i % columns; [iv mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.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.leading.trailing.equalTo(self.cardView).inset(15); make.top.equalTo(self.contentLabel.mas_bottom).offset(12); make.height.mas_equalTo(height); }]; // 强制触发布局更新,确保 cell 高度正确计算 [self.contentView setNeedsLayout]; [self.contentView layoutIfNeeded]; } /// 格式化时间戳为 MM/dd 格式 - (NSString *)formatTimestampToDate:(NSString *)timestampString { if (!timestampString || timestampString.length == 0) { return @""; } // 将字符串转换为时间戳(毫秒) NSTimeInterval timestamp = [timestampString doubleValue] / 1000.0; if (timestamp <= 0) { return @""; } NSDate *date = [NSDate dateWithTimeIntervalSince1970:timestamp]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"MM/dd"; return [formatter stringFromDate:date]; } /// 格式化时间戳为相对时间 - (NSString *)formatTimeInterval:(NSInteger)timestamp { if (timestamp <= 0) return YMLocalizedString(@"time.just_now"); NSTimeInterval interval = [[NSDate date] timeIntervalSince1970] - timestamp / 1000.0; if (interval < 60) { return YMLocalizedString(@"time.just_now"); } else if (interval < 3600) { return [NSString stringWithFormat:YMLocalizedString(@"time.minutes_ago"), interval / 60]; } else if (interval < 86400) { return [NSString stringWithFormat:YMLocalizedString(@"time.hours_ago"), interval / 3600]; } else if (interval < 604800) { return [NSString stringWithFormat:YMLocalizedString(@"time.days_ago"), interval / 86400]; } else { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"yyyy-MM-dd"; return [formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:timestamp / 1000.0]]; } } // MARK: - Actions - (void)onLikeButtonTapped { if (!self.currentModel) return; // 如果已点赞,执行取消点赞 if (self.currentModel.isLike) { [self performLikeAction:NO]; return; } // 审核中的动态不可点赞 if (self.currentModel.status == 0) { NSLog(@"[EPMomentCell] 动态审核中,无法点赞"); // TODO: 可选择显示提示 Toast return; } // 执行点赞 [self performLikeAction:YES]; } - (void)performLikeAction:(BOOL)isLike { NSLog(@"[EPMomentCell] %@ 动态: %@", isLike ? @"点赞" : @"取消点赞", self.currentModel.dynamicId); NSString *dynamicId = self.currentModel.dynamicId; NSString *likedUid = self.currentModel.uid; long worldId = self.currentModel.worldId; // 使用 Swift API Helper @kWeakify(self); [self.apiHelper likeMomentWithDynamicId:dynamicId isLike:isLike likedUid:likedUid worldId:worldId completion:^{ @kStrongify(self); // 更新点赞状态 self.currentModel.isLike = isLike; NSInteger likeCount = [self.currentModel.likeCount integerValue]; likeCount += isLike ? 1 : -1; likeCount = MAX(0, likeCount); // 防止负数 self.currentModel.likeCount = @(likeCount).stringValue; // 更新 UI self.likeButton.selected = self.currentModel.isLike; [self.likeButton setTitle:[NSString stringWithFormat:@" %ld", (long)likeCount] forState:UIControlStateNormal]; [self.likeButton setTitle:[NSString stringWithFormat:@" %ld", (long)likeCount] forState:UIControlStateSelected]; NSLog(@"[EPMomentCell] %@ 成功", isLike ? @"点赞" : @"取消点赞"); } failure:^(NSInteger code, NSString * _Nonnull msg) { NSLog(@"[EPMomentCell] %@ 失败 (code: %ld): %@", isLike ? @"点赞" : @"取消点赞", (long)code, msg); }]; } // 评论功能已隐藏 // - (void)onCommentButtonTapped { // NSLog(@"[EPMomentCell] 评论"); // } - (void)onImageTapped:(UITapGestureRecognizer *)gesture { if (!self.currentModel || !self.currentModel.dynamicResList.count) return; NSInteger index = gesture.view.tag; NSLog(@"[EPMomentCell] 点击图片索引: %ld", (long)index); SDPhotoBrowser *browser = [[SDPhotoBrowser alloc] init]; browser.sourceImagesContainerView = self.imagesContainer; browser.delegate = self; browser.imageCount = self.currentModel.dynamicResList.count; browser.currentImageIndex = index; [browser show]; } #pragma mark - SDPhotoBrowserDelegate - (NSURL *)photoBrowser:(SDPhotoBrowser *)browser highQualityImageURLForIndex:(NSInteger)index { if (index >= 0 && index < self.currentModel.dynamicResList.count) { id item = self.currentModel.dynamicResList[index]; NSString *url = nil; if ([item isKindOfClass:[NSDictionary class]]) { url = [item valueForKey:@"resUrl"] ?: [item valueForKey:@"url"]; } else if ([item respondsToSelector:@selector(resUrl)]) { url = [item valueForKey:@"resUrl"]; } if (url) { return [NSURL URLWithString:url]; } } return nil; } - (UIImage *)photoBrowser:(SDPhotoBrowser *)browser placeholderImageForIndex:(NSInteger)index { return [UIImageConstant defaultBannerPlaceholder]; } // MARK: - Lazy Loading - (UIView *)cardView { if (!_cardView) { _cardView = [[UIView alloc] init]; _cardView.backgroundColor = [UIColor clearColor]; // 透明背景,颜色由 colorBackgroundView 提供 _cardView.layer.cornerRadius = 12; // 圆角 // Shadow 将由 applyEmotionColorEffect 动态设置 _cardView.layer.masksToBounds = NO; } return _cardView; } - (UIView *)colorBackgroundView { if (!_colorBackgroundView) { _colorBackgroundView = [[UIView alloc] init]; _colorBackgroundView.layer.cornerRadius = 12; _colorBackgroundView.layer.masksToBounds = YES; } return _colorBackgroundView; } - (UIVisualEffectView *)blurEffectView { if (!_blurEffectView) { UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; _blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; _blurEffectView.layer.cornerRadius = 12; _blurEffectView.layer.masksToBounds = YES; } return _blurEffectView; } - (UIImageView *)avatarImageView { if (!_avatarImageView) { NetImageConfig *config = [[NetImageConfig alloc] init]; _avatarImageView = [[NetImageView alloc] initWithConfig:config]; _avatarImageView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0]; _avatarImageView.layer.cornerRadius = 20; _avatarImageView.layer.masksToBounds = YES; _avatarImageView.contentMode = UIViewContentModeScaleAspectFill; } return _avatarImageView; } - (UILabel *)nameLabel { if (!_nameLabel) { _nameLabel = [[UILabel alloc] init]; _nameLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium]; _nameLabel.textColor = [UIColor whiteColor]; } return _nameLabel; } - (UILabel *)timeLabel { if (!_timeLabel) { _timeLabel = [[UILabel alloc] init]; _timeLabel.font = [UIFont systemFontOfSize:12]; _timeLabel.textColor = [UIColor colorWithWhite:1 alpha:0.6]; } return _timeLabel; } - (UILabel *)contentLabel { if (!_contentLabel) { _contentLabel = [[UILabel alloc] init]; _contentLabel.font = [UIFont systemFontOfSize:15]; _contentLabel.textColor = [UIColor whiteColor]; _contentLabel.numberOfLines = 0; _contentLabel.lineBreakMode = NSLineBreakByWordWrapping; } return _contentLabel; } - (UIView *)actionBar { if (!_actionBar) { _actionBar = [[UIView alloc] init]; _actionBar.backgroundColor = [UIColor clearColor]; // 半透明白色,与毛玻璃效果搭配 } return _actionBar; } - (UIButton *)likeButton { if (!_likeButton) { _likeButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_likeButton setImage:[UIImage imageNamed:@"monents_info_like_count_normal"] forState:UIControlStateNormal]; [_likeButton setImage:[UIImage imageNamed:@"monents_info_like_count_select"] forState:UIControlStateSelected]; [_likeButton setTitle:@" 0" forState:UIControlStateNormal]; _likeButton.titleLabel.font = [UIFont systemFontOfSize:13]; [_likeButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.6] forState:UIControlStateNormal]; [_likeButton setTitleColor:[UIColor colorWithWhite:1 alpha:1.0] forState:UIControlStateSelected]; [_likeButton addTarget:self action:@selector(onLikeButtonTapped) forControlEvents:UIControlEventTouchUpInside]; } return _likeButton; } // 评论按钮已移除 - (UIButton *)commentButton { return nil; } - (UIButton *)createActionButtonWithTitle:(NSString *)title { UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; [button setTitle:title forState:UIControlStateNormal]; button.titleLabel.font = [UIFont systemFontOfSize:13]; [button setTitleColor:[UIColor colorWithWhite:0.5 alpha:1.0] forState:UIControlStateNormal]; 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; } - (EPMomentAPISwiftHelper *)apiHelper { if (!_apiHelper) { _apiHelper = [[EPMomentAPISwiftHelper alloc] init]; } return _apiHelper; } @end