Files
real-e-party-iOS/YuMi/E-P/NewMoments/Views/EPMomentCell.m
edwinQQQ c8173bf034 refactor: 移除 Core Data 相关代码并添加新的消息列表视图控制器
主要变更:
1. 从 AppDelegate 中移除 Core Data 相关的属性和方法,简化应用结构。
2. 新增 EPBaseListViewController 作为消息列表的基础类,提供通用的表视图功能。
3. 添加 EPMessageListVC、EPFriendListVC、EPFollowingListVC 和 EPFansListVC,分别用于展示消息、朋友、关注和粉丝列表。
4. 引入 EPMessageSegmentView 以支持消息主界面的分段控制。

此更新旨在提升代码的可维护性,简化数据管理,并增强用户界面的功能性和交互性。
2025-10-20 11:25:33 +08:00

557 lines
18 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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"
@interface EPMomentCell () <SDPhotoBrowserDelegate>
// 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<NetImageView *> *imageViews;
@property (nonatomic, strong) UIView *actionBar;
@property (nonatomic, strong) UIButton *likeButton;
@property (nonatomic, strong) UIButton *commentButton;
@property (nonatomic, strong) MomentsInfoModel *currentModel;
@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);
}];
[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");
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;
[self applyEmotionColorEffect:model.emotionColor];
[self setNeedsLayout];
}
- (void)applyEmotionColorEffect:(NSString *)emotionColorHex {
if (!emotionColorHex) {
NSLog(@"[EPMomentCell] 警告emotionColorHex 为 nil");
return;
}
UIColor *color = [self colorFromHex:emotionColorHex];
self.cardView.layer.borderWidth = 0;
self.colorBackgroundView.backgroundColor = [color colorWithAlphaComponent:0.5];
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;
}
- (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;
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);
}];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
}
- (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;
@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;
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)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];
_cardView.layer.cornerRadius = 12;
_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<NetImageView *> *)imageViews {
if (!_imageViews) {
_imageViews = [NSMutableArray array];
}
return _imageViews;
}
- (EPMomentAPISwiftHelper *)apiHelper {
if (!_apiHelper) {
_apiHelper = [[EPMomentAPISwiftHelper alloc] init];
}
return _apiHelper;
}
@end