diff --git a/.cursor/rules/next-chat.mdc b/.cursor/rules/next-chat.mdc new file mode 100644 index 00000000..6053d0bb --- /dev/null +++ b/.cursor/rules/next-chat.mdc @@ -0,0 +1,7 @@ +--- +description: +globs: +alwaysApply: false +--- +本次对话的上下文已经太长了,我打算关掉并重新开一个新的会话。 +你有什么想对你的继任者说的,以便它能更好的理解你当前的工作并顺利继续? \ No newline at end of file diff --git a/YuMi/Assets.xcassets/20.20.61/brown_arrow.imageset/Contents.json b/YuMi/Assets.xcassets/20.20.61/brown_arrow.imageset/Contents.json new file mode 100644 index 00000000..777706f2 --- /dev/null +++ b/YuMi/Assets.xcassets/20.20.61/brown_arrow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "切图 31@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/YuMi/Assets.xcassets/20.20.61/brown_arrow.imageset/切图 31@3x.png b/YuMi/Assets.xcassets/20.20.61/brown_arrow.imageset/切图 31@3x.png new file mode 100644 index 00000000..41e8b82a Binary files /dev/null and b/YuMi/Assets.xcassets/20.20.61/brown_arrow.imageset/切图 31@3x.png differ diff --git a/YuMi/Assets.xcassets/20.20.61/medals_empty_other.imageset/Contents.json b/YuMi/Assets.xcassets/20.20.61/medals_empty_other.imageset/Contents.json new file mode 100644 index 00000000..b7cf6ebc --- /dev/null +++ b/YuMi/Assets.xcassets/20.20.61/medals_empty_other.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "切图 70@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/YuMi/Assets.xcassets/20.20.61/medals_empty_other.imageset/切图 70@3x.png b/YuMi/Assets.xcassets/20.20.61/medals_empty_other.imageset/切图 70@3x.png new file mode 100644 index 00000000..ece9f994 Binary files /dev/null and b/YuMi/Assets.xcassets/20.20.61/medals_empty_other.imageset/切图 70@3x.png differ diff --git a/YuMi/Assets.xcassets/20.20.61/medals_icon_other.imageset/Contents.json b/YuMi/Assets.xcassets/20.20.61/medals_icon_other.imageset/Contents.json new file mode 100644 index 00000000..c347eef8 --- /dev/null +++ b/YuMi/Assets.xcassets/20.20.61/medals_icon_other.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "容器 8456@3x (1).png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/YuMi/Assets.xcassets/20.20.61/medals_icon_other.imageset/容器 8456@3x (1).png b/YuMi/Assets.xcassets/20.20.61/medals_icon_other.imageset/容器 8456@3x (1).png new file mode 100644 index 00000000..988a34d1 Binary files /dev/null and b/YuMi/Assets.xcassets/20.20.61/medals_icon_other.imageset/容器 8456@3x (1).png differ diff --git a/YuMi/Assets.xcassets/20.20.61/medals_icon_rank.imageset/Contents.json b/YuMi/Assets.xcassets/20.20.61/medals_icon_rank.imageset/Contents.json new file mode 100644 index 00000000..576b82d7 --- /dev/null +++ b/YuMi/Assets.xcassets/20.20.61/medals_icon_rank.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "容器 8456@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/YuMi/Assets.xcassets/20.20.61/medals_icon_rank.imageset/容器 8456@3x.png b/YuMi/Assets.xcassets/20.20.61/medals_icon_rank.imageset/容器 8456@3x.png new file mode 100644 index 00000000..23aaf2ac Binary files /dev/null and b/YuMi/Assets.xcassets/20.20.61/medals_icon_rank.imageset/容器 8456@3x.png differ diff --git a/YuMi/Modules/YMMessage/Model/AttachmentModel.h b/YuMi/Modules/YMMessage/Model/AttachmentModel.h index daf87c30..1e8853e4 100644 --- a/YuMi/Modules/YMMessage/Model/AttachmentModel.h +++ b/YuMi/Modules/YMMessage/Model/AttachmentModel.h @@ -723,7 +723,7 @@ typedef NS_ENUM(NSUInteger, CustomMessageTypeSuperGift) { Custom_Message_Sub_Super_Gift_Banner = 1066, // 飘屏 }; ///通用飘屏 -//CustomMessageType_General_Floating_Screen = 105, +//CustomMessageType_General_Floating_Screen = 107, typedef NS_ENUM(NSUInteger, CustomMessageTypeGeneralFloatingScreen) { ///所有房间 Custom_Message_Sub_General_Floating_Screen_One_Room = 1071,//单房间 diff --git a/YuMi/Modules/YMMessage/View/Session/SessionViewController.h b/YuMi/Modules/YMMessage/View/Session/SessionViewController.h index 97fb9d08..3d44e1c0 100644 --- a/YuMi/Modules/YMMessage/View/Session/SessionViewController.h +++ b/YuMi/Modules/YMMessage/View/Session/SessionViewController.h @@ -8,7 +8,7 @@ #import "MvpViewController.h" #import "SessionListViewController.h" #import - +@class UserInfoModel; NS_ASSUME_NONNULL_BEGIN @interface SessionViewController : MvpViewController @@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN ///置顶回话 @property (nonatomic,strong) NSMutableDictionary *stickTopMessages; +@property (nonatomic, strong) UserInfoModel *userInfo; + @end NS_ASSUME_NONNULL_END diff --git a/YuMi/Modules/YMMessage/View/Session/SessionViewController.m b/YuMi/Modules/YMMessage/View/Session/SessionViewController.m index 0e07b7fe..38651cc0 100644 --- a/YuMi/Modules/YMMessage/View/Session/SessionViewController.m +++ b/YuMi/Modules/YMMessage/View/Session/SessionViewController.m @@ -104,7 +104,7 @@ ///导航栏 @property (nonatomic,strong) SessionNavView *sessionNavView; @property (nonatomic, strong) UITableView * sessionTableView; -@property (nonatomic, strong) UserInfoModel *userInfo; + @property (nonatomic, strong) UserInfoModel *detailUserInfo; ///最后的一条消息 @property (nonatomic,strong) NIMMessage *lastMessage; @@ -159,7 +159,9 @@ [super viewWillAppear:animated]; if (![[[ClientConfig shareConfig].configInfo officialAccountUids] containsObject:self.session.sessionId]) { [self.presenter getFansLike:self.session.sessionId]; - [self.presenter getUserInfoWithUid:self.session.sessionId]; + if (!self.userInfo) { + [self.presenter getUserInfoWithUid:self.session.sessionId]; + } } } @@ -258,7 +260,6 @@ default: model = [[MessageUnSupportModel alloc] initWithMessage:message]; break; - } return model; } @@ -595,8 +596,8 @@ [XPSkillCardPlayerManager shareInstance].userInfoModel = userInfo; } else { self.userInfo = userInfo; - [self.sessionTableView reloadData]; } + [self.sessionTableView reloadData]; } #pragma mark - MessageCellDelegate diff --git a/YuMi/Modules/YMMessage/View/SessionList/SessionListViewController.h b/YuMi/Modules/YMMessage/View/SessionList/SessionListViewController.h index abaa4b39..cc427253 100644 --- a/YuMi/Modules/YMMessage/View/SessionList/SessionListViewController.h +++ b/YuMi/Modules/YMMessage/View/SessionList/SessionListViewController.h @@ -7,7 +7,7 @@ // 请注意,这是一次冒险。😱 // -#import "BaseViewController.h" +#import "MvpViewController.h" #import #import #import @@ -19,7 +19,7 @@ typedef NS_ENUM(NSUInteger, SessionListOpenType) { SessionListOpenTypeRoom = 2, }; -@interface SessionListViewController : BaseViewController +@interface SessionListViewController : MvpViewController - (instancetype)initWithType:(SessionListOpenType)type; /** 控制器 因为房间内聊天没有控制器去push 或者做其他的操作*/ diff --git a/YuMi/Modules/YMMessage/View/SessionList/SessionListViewController.m b/YuMi/Modules/YMMessage/View/SessionList/SessionListViewController.m index 5fcbb7b7..21ab094a 100644 --- a/YuMi/Modules/YMMessage/View/SessionList/SessionListViewController.m +++ b/YuMi/Modules/YMMessage/View/SessionList/SessionListViewController.m @@ -20,13 +20,14 @@ #import "XPSessionFindNewViewController.h" #import "TTPopUp.h" #import "XPSkillCardPlayerManager.h" - +#import "MessagePresenter.h" +#import "MessageProtocol.h" NSString * const kMessageShowReadDotKey = @"kMessageShowReadDotKey"; #import -@interface SessionListViewController () +@interface SessionListViewController () /** * 会话列表 @@ -42,6 +43,9 @@ NSString * const kMessageShowReadDotKey = @"kMessageShowReadDotKey"; ///用户信息 @property (nonatomic,strong) UserInfoModel *userInfo; @property (nonatomic, copy) void(^scrollCallback)(UIScrollView *scrollView); + +@property (nonatomic, strong) NIMRecentSession *recentSession; + @end @implementation SessionListViewController @@ -56,6 +60,10 @@ NSString * const kMessageShowReadDotKey = @"kMessageShowReadDotKey"; return YES; } +- (MessagePresenter *)createPresenter { + return [[MessagePresenter alloc] init]; +} + - (instancetype)initWithType:(SessionListOpenType)type { self = [self init]; if (self) { @@ -137,6 +145,15 @@ NSString * const kMessageShowReadDotKey = @"kMessageShowReadDotKey"; }]; } +- (void)onGetUserInfoSuccess:(UserInfoModel *)userInfo { + if (userInfo) { + SessionViewController *vc = [[SessionViewController alloc] initWithSession:self.recentSession.session]; + vc.openType = self.openType; + vc.userInfo = userInfo; + [self.navigationController pushViewController:vc animated:YES]; + } +} + #pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ if (self.recentSessions.count == 0) { @@ -157,9 +174,14 @@ NSString * const kMessageShowReadDotKey = @"kMessageShowReadDotKey"; [self.mainController addChildViewController:sessionVC]; } else { NIMRecentSession *recentSession = self.recentSessions[indexPath.row]; - SessionViewController *vc = [[SessionViewController alloc] initWithSession:recentSession.session]; - vc.openType = self.openType; - [self.navigationController pushViewController:vc animated:YES]; + self.recentSession = recentSession; + if ([[[ClientConfig shareConfig].configInfo officialAccountUids] containsObject:self.recentSession.session.sessionId]) { + SessionViewController *vc = [[SessionViewController alloc] initWithSession:self.recentSession.session]; + vc.openType = self.openType; + [self.navigationController pushViewController:vc animated:YES]; + } else { + [self.presenter getUserInfoWithUid:recentSession.session.sessionId]; + } } } diff --git a/YuMi/Modules/YMMine/Model/Medals/MedalsModel.m b/YuMi/Modules/YMMine/Model/Medals/MedalsModel.m index 260c44c2..6e84899b 100644 --- a/YuMi/Modules/YMMine/Model/Medals/MedalsModel.m +++ b/YuMi/Modules/YMMine/Model/Medals/MedalsModel.m @@ -15,8 +15,8 @@ return YMLocalizedString(@"20.20.61_text_9"); } - // 将秒转换为 NSDate - NSDate *expireDate = [NSDate dateWithTimeIntervalSince1970:self.expireSeconds]; + // 当前时间 + expireSeconds 得到目标时间 + NSDate *expireDate = [NSDate dateWithTimeIntervalSinceNow:self.expireSeconds]; // 创建日期格式化器 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; diff --git a/YuMi/Modules/YMMine/Model/Recharge/RechargeListModel.h b/YuMi/Modules/YMMine/Model/Recharge/RechargeListModel.h index 20a65185..ee1ea410 100644 --- a/YuMi/Modules/YMMine/Model/Recharge/RechargeListModel.h +++ b/YuMi/Modules/YMMine/Model/Recharge/RechargeListModel.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN //充值banner位数据 @property(nonatomic,copy) NSString *bannerUrl; @property(nonatomic,copy) NSString *linkUrl; +@property (nonatomic, assign) NSInteger chargeGoldNum; @end diff --git a/YuMi/Modules/YMMine/Model/Recharge/WalletInfoModel.h b/YuMi/Modules/YMMine/Model/Recharge/WalletInfoModel.h index a63ae9f7..64d958bc 100644 --- a/YuMi/Modules/YMMine/Model/Recharge/WalletInfoModel.h +++ b/YuMi/Modules/YMMine/Model/Recharge/WalletInfoModel.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy)NSString *diamonds; //金币数量 @property(nonatomic, assign) double golds; -@property(nonatomic, copy)NSString *chargeGoldNum; +@property(nonatomic, copy) NSString *chargeGoldNum; @property(nonatomic, assign)NSInteger amount; /// 钻石数量 diff --git a/YuMi/Modules/YMMine/Presenter/MedalsPresenter.h b/YuMi/Modules/YMMine/Presenter/MedalsPresenter.h index e8a785d6..c4ad23d1 100644 --- a/YuMi/Modules/YMMine/Presenter/MedalsPresenter.h +++ b/YuMi/Modules/YMMine/Presenter/MedalsPresenter.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)userMedalsSuccess:(UserMedalsModel *)userMedalsModel; - (void)userMedalsFailure; -- (void)squareMedalsSuccess:(NSArray *)squareMedalsModel; +- (void)squareMedalsSuccess:(NSArray *)squareMedalsModel; - (void)squareMedalsFailure; - (void)mineAllMedalsSuccess:(MineAllMedalModel *)model; diff --git a/YuMi/Modules/YMMine/View/Cell/MineInfo/XPMineMultipleContentTableViewCell.m b/YuMi/Modules/YMMine/View/Cell/MineInfo/XPMineMultipleContentTableViewCell.m index dfbb5791..32557983 100644 --- a/YuMi/Modules/YMMine/View/Cell/MineInfo/XPMineMultipleContentTableViewCell.m +++ b/YuMi/Modules/YMMine/View/Cell/MineInfo/XPMineMultipleContentTableViewCell.m @@ -156,6 +156,7 @@ } - (void)setUserMedalModel:(UserMedalModel *)userMedalModel { + _userMedalModel = userMedalModel; self.icon.hidden = YES; self.vapView.hidden = NO; @@ -168,21 +169,99 @@ }]; self.nameLabel.text = userMedalModel.medalName; - if (self.videoUrl.length > 0) { - [self.vapView playHWDMP4:self.videoUrl repeatCount:-1 delegate:nil]; - } else { - NSString *resourcePath = [userMedalModel.picUrl pureURLString]; - if (resourcePath.length > 0) { - @kWeakify(self); - [self.vapParser parseWithURL:resourcePath completionBlock:^(NSString * _Nullable videoUrl) { - @kStrongify(self); - if (videoUrl.length) { - [self.vapView playHWDMP4:videoUrl repeatCount:-1 delegate:nil]; - } - } failureBlock:^(NSError * _Nullable error) { - }]; - } + + // 停止之前的播放 + [self.vapView stopHWDMP4]; + + // 按照新的优先级逻辑处理显示 + NSString *mp4Url = userMedalModel.mp4Url; + NSString *picUrl = userMedalModel.picUrl; + + // 1. 优先使用 mp4Url 展示 vapView 内容(判断是否有效 mp4) + if (![NSString isEmpty:mp4Url] && [self isValidMP4URL:mp4Url]) { + [self playMP4WithUrl:mp4Url]; + return; } + + // 2. mp4Url 无效,检查 picUrl 是否为有效图片 + if (![NSString isEmpty:picUrl] && [NSString isValidImageURL:picUrl]) { + // 显示图片内容 + [self showImageWithUrl:picUrl]; + return; + } + + // 3. picUrl 不是有效图片,尝试使用 picUrl 展示 vapView 内容 + if (![NSString isEmpty:picUrl]) { + [self playMP4WithUrl:picUrl]; + return; + } + + // 所有条件都不满足,显示默认状态 + [self showDefaultState]; +} + +#pragma mark - 私有方法 + +/// 验证是否为有效的 MP4 URL +- (BOOL)isValidMP4URL:(NSString *)url { + if ([NSString isEmpty:url]) { + return NO; + } + + NSString *lowercaseUrl = [url lowercaseString]; + return [lowercaseUrl hasSuffix:@".mp4"] || + [lowercaseUrl containsString:@"mp4"] || + [lowercaseUrl containsString:@"video"]; +} + +/// 验证是否为有效的图片 URL +- (BOOL)isValidImageURL:(NSString *)url { + if ([NSString isEmpty:url]) { + return NO; + } + + NSString *lowercaseUrl = [url lowercaseString]; + return [lowercaseUrl hasSuffix:@".jpg"] || + [lowercaseUrl hasSuffix:@".jpeg"] || + [lowercaseUrl hasSuffix:@".png"] || + [lowercaseUrl hasSuffix:@".gif"] || + [lowercaseUrl hasSuffix:@".webp"] || + [lowercaseUrl containsString:@"image"]; +} + +/// 播放 MP4 内容 +- (void)playMP4WithUrl:(NSString *)url { + self.vapView.hidden = NO; + self.icon.hidden = YES; + + NSString *resourcePath = [url pureURLString]; + if (resourcePath.length > 0) { + @kWeakify(self); + [self.vapParser parseWithURL:resourcePath completionBlock:^(NSString * _Nullable videoUrl) { + @kStrongify(self); + if (videoUrl.length) { + [self.vapView playHWDMP4:videoUrl repeatCount:-1 delegate:nil]; + } + } failureBlock:^(NSError * _Nullable error) { + @kStrongify(self); + // MP4 播放失败,显示默认状态 + [self showDefaultState]; + }]; + } +} + +/// 显示图片内容 +- (void)showImageWithUrl:(NSString *)url { + self.vapView.hidden = YES; + self.icon.hidden = NO; + self.icon.imageUrl = url; +} + +/// 显示默认状态 +- (void)showDefaultState { + self.vapView.hidden = YES; + self.icon.hidden = NO; + self.icon.imageUrl = @""; // 显示默认占位图 } - (NetImageView *)icon { diff --git a/YuMi/Modules/YMMine/View/IncomeRecord/XPIncomeRecordVC.m b/YuMi/Modules/YMMine/View/IncomeRecord/XPIncomeRecordVC.m index 7f59106f..a75ce3de 100644 --- a/YuMi/Modules/YMMine/View/IncomeRecord/XPIncomeRecordVC.m +++ b/YuMi/Modules/YMMine/View/IncomeRecord/XPIncomeRecordVC.m @@ -76,6 +76,7 @@ - (void)handleTapFirstCharge { XPWebViewController *webVC = [[XPWebViewController alloc] initWithRoomUID:@""]; webVC.url = URLWithType(kFirstChargeBanner); + webVC.isPush = YES; [self.navigationController pushViewController:webVC animated:YES]; } diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell.h b/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell.h index 4fecc55f..c9e3a486 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell.h +++ b/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell.h @@ -6,7 +6,7 @@ // #import -@class MedalSeriesVo; +@class MedalSeriesItemVo; NS_ASSUME_NONNULL_BEGIN @interface MedalsCollectionViewCell : UICollectionViewCell @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN + (void)registerTo:(UICollectionView *)collectionView; + (instancetype)cellFor:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)index; -- (void)updateCell:(MedalSeriesVo *)model; +- (void)updateCell:(MedalSeriesItemVo *)model isForSquare:(BOOL)isSquare; /** * 当 cell 将要显示时调用 diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell.m b/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell.m index 82c55aa3..f021b53e 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell.m +++ b/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell.m @@ -146,9 +146,12 @@ // 停止播放 [self stopMP4Playback]; - // 隐藏 mp4 视图 + // 更彻底地重置 mp4View + [self resetMP4View]; + + // 隐藏视图 self.mp4View.hidden = YES; - self.imageView.hidden = NO; + self.imageView.hidden = YES; // 重置状态 self.mp4Path = nil; @@ -157,6 +160,7 @@ self.displayModel = nil; // 重置数据模型 self.currentItemVo = nil; // 重置当前项 + // 清空文本 self.titleLabel.text = @""; self.subLabel.text = @""; @@ -169,27 +173,26 @@ self.imageView.imageUrl = @""; } -- (void)updateCell:(MedalSeriesVo *)model { - MedalSeriesItemVo *itemVos = [model.medalSeries xpSafeObjectAtIndex:0]; - self.currentItemVo = itemVos; - self.displayModel = [itemVos.medalVos xpSafeObjectAtIndex:0]; +- (void)updateCell:(MedalSeriesItemVo *)model isForSquare:(BOOL)isSquare { +// MedalSeriesItemVo *itemVos = [model.medalSeries xpSafeObjectAtIndex:0]; + self.currentItemVo = model; - // 配置等级指示器 - [self.levelIndicatorView setupWithMaxLevel:itemVos.medalLevel]; - [self.levelIndicatorView setSelectedLevel:1 animated:NO]; - - // 设置指示器类型为带图片 + // 设置指示器类型为不带图片 self.levelIndicatorView.indicatorType = MedalsLevelIndicatorTypeNormal; - // 为每个等级设置对应的图片,使用新的 URL 优先级逻辑 - for (NSInteger i = 0; i < itemVos.medalVos.count; i++) { - MedalVo *medalVo = [itemVos.medalVos xpSafeObjectAtIndex:i]; - if (medalVo) { - NSString *imageUrl = [self getImageUrlForMedal:medalVo]; - [self.levelIndicatorView setImageUrl:imageUrl forLevel:i + 1]; - } + // 配置等级指示器 + [self.levelIndicatorView setupWithMaxLevel:model.medalLevel]; + + if (isSquare) { + self.displayModel = [model.medalVos xpSafeObjectAtIndex:model.medalVos.count - 1]; + [self.levelIndicatorView setSelectedLevel:model.medalLevel animated:NO]; + } else { + self.displayModel = [model.medalVos xpSafeObjectAtIndex:0]; + [self.levelIndicatorView setSelectedLevel:1 animated:NO]; } + self.levelIndicatorView.userInteractionEnabled = !isSquare; + [self updateDisplayWithCurrentModel]; } @@ -199,11 +202,9 @@ return; } - // TODO: 要手动 fix mp4 不播放的问题 - // 优化后的判断逻辑:更严格的 MP4 URL 验证 - NSString *mp4Url = self.currentItemVo.mp4Url; - NSString *picUrl = self.currentItemVo.picUrl; + NSString *mp4Url = self.displayModel.mp4Url; + NSString *picUrl = self.displayModel.picUrl; // 首先检查是否有明确的 MP4 URL if (![NSString isEmpty:mp4Url] && [self isValidMP4URL:mp4Url]) { @@ -229,8 +230,7 @@ [self showDefaultPlaceholder]; // 设置文本信息(无论是否有有效的媒体内容都要设置) - self.titleLabel.text = self.displayModel.name; - self.subLabel.text = [self.displayModel expireDateString]; + [self updateTextLabels]; } #pragma mark - 私有方法 @@ -296,6 +296,10 @@ } - (void)setImagePath:(NSString *)imagePath { + if ([NSString isEmpty:imagePath]) { + self.imageView.hidden = YES; + return; + } // 停止之前的 mp4 播放 [self stopMP4Playback]; @@ -306,8 +310,8 @@ } - (void)setMp4Path:(NSString *)mp4Path { - // 如果是相同的 mp4 路径,不需要重新加载 - if ([_mp4Path isEqualToString:mp4Path]) { + if ([NSString isEmpty:mp4Path]) { + self.mp4View.hidden = YES; return; } @@ -366,6 +370,35 @@ #pragma mark - MP4 播放控制 +/// 重置 MP4 视图,清理所有缓存内容 +- (void)resetMP4View { + if (self.mp4View) { + // 方案1: 基础重置(推荐) + [self.mp4View stopHWDMP4]; + self.mp4View.tag = 0; + self.mp4View.hidden = YES; + + // 方案2: 完全重置(如果遇到严重的缓存问题才启用) + if (YES) { // 根据需要设置为 NO 来禁用完全重置 + [self.mp4View removeFromSuperview]; + _mp4View = nil; + // 重新添加 mp4View + [self.contentView addSubview:self.mp4View]; + [self.mp4View mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(self.imageView); + }]; + } + + // 方案3: 强制清理内存(备用方案) + // 通过设置新的frame来触发内部清理 + if (NO) { // 可以启用此方案作为替代 + CGRect currentFrame = self.mp4View.frame; + self.mp4View.frame = CGRectZero; + self.mp4View.frame = currentFrame; + } + } +} + - (void)stopMP4Playback { if (self.mp4View) { [self.mp4View stopHWDMP4]; @@ -375,7 +408,7 @@ - (void)pauseMP4Playback { if (self.mp4View && !self.mp4View.hidden) { - [self.mp4View pauseHWDMP4]; + [self.mp4View stopHWDMP4]; } } @@ -404,6 +437,7 @@ NSLog(@"%@", error); } + #pragma mark - HWDMP4PlayDelegate - (BOOL)shouldStartPlayMP4:(VAPView *)container config:(QGVAPConfigModel *)config { @@ -428,6 +462,7 @@ - (void)willDisplay { self.isVisible = YES; +// return; // 如果有准备好的 MP4,立即开始播放 if (self.mp4View.tag == 1 && !self.mp4View.hidden && self.mp4Path) { @@ -482,7 +517,8 @@ - (VAPView *)mp4View { if (!_mp4View) { _mp4View = [[VAPView alloc] init]; - _mp4View.contentMode = UIViewContentModeScaleAspectFill; + _mp4View.contentMode = UIViewContentModeScaleAspectFit; +// [_mp4View enableOldVersion:YES]; } return _mp4View; } diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell_Refactored.m b/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell_Refactored.m new file mode 100644 index 00000000..7fd6a715 --- /dev/null +++ b/YuMi/Modules/YMMine/View/Medals/MedalsCollectionViewCell_Refactored.m @@ -0,0 +1,214 @@ +// +// MedalsCollectionViewCell_Refactored.m +// YuMi +// +// 重构示例:使用 MedalMediaDisplayManager 简化媒体处理逻辑 +// + +#import "MedalsCollectionViewCell.h" +#import "MedalsModel.h" +#import "MedalMediaDisplayManager.h" +#import "MedalsLevelIndicatorView.h" + +@interface MedalsCollectionViewCell () + +// 媒体显示管理器 - 替代原来的所有媒体相关属性和方法 +@property (nonatomic, strong) MedalMediaDisplayManager *mediaManager; + +// UI 元素 +@property (nonatomic, strong) NetImageView *imageView; +@property (nonatomic, strong) VAPView *mp4View; +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) UILabel *subLabel; +@property (nonatomic, strong) MedalsLevelIndicatorView *levelIndicatorView; + +// 数据模型 +@property (nonatomic, strong) MedalVo *displayModel; +@property (nonatomic, strong) MedalSeriesItemVo *currentItemVo; + +@end + +@implementation MedalsCollectionViewCell + ++ (NSString *)cellID { + return NSStringFromClass([MedalsCollectionViewCell class]); +} + ++ (void)registerTo:(UICollectionView *)collectionView { + [collectionView registerClass:[self class] forCellWithReuseIdentifier:[self cellID]]; +} + ++ (instancetype)cellFor:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)index { + MedalsCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[self cellID] + forIndexPath:index]; + return cell; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setupUI]; + [self setupMediaManager]; + } + return self; +} + +- (void)setupUI { + // UI 设置保持不变 + [self.contentView addGradientBackgroundWithColors:@[ + UIColorFromRGB(0x41007b), + UIColorFromRGB(0x290858) + ] startPoint:CGPointMake(0.5, 0) endPoint:CGPointMake(0.5, 1) cornerRadius:8]; + + [self.contentView setAllCornerRadius:8 + borderWidth:1 + borderColor:UIColorFromRGB(0xa166bf)]; + + self.imageView = [[NetImageView alloc] init]; + self.imageView.contentMode = UIViewContentModeScaleAspectFill; + [self.contentView addSubview:self.imageView]; + [self.imageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self.contentView); + make.top.mas_equalTo(13); + make.leading.trailing.mas_equalTo(self.contentView).inset(13); + make.height.mas_equalTo(self.imageView.mas_width); + }]; + + self.mp4View = [[VAPView alloc] init]; + self.mp4View.contentMode = UIViewContentModeScaleAspectFit; + [self.contentView addSubview:self.mp4View]; + [self.mp4View mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(self.imageView); + }]; + + // 其他UI元素设置... + self.titleLabel = [UILabel labelInitWithText:@"" font:kFontMedium(14) textColor:[UIColor whiteColor]]; + self.subLabel = [UILabel labelInitWithText:@"" font:kFontRegular(11) textColor:[UIColor colorWithWhite:1 alpha:0.6]]; + // ... 约束设置省略 +} + +- (void)setupMediaManager { + // 创建媒体管理器,传入自己作为代理 + self.mediaManager = [[MedalMediaDisplayManager alloc] initWithDelegate:self]; +} + +- (void)prepareForReuse { + [super prepareForReuse]; + + // 使用媒体管理器清理资源 - 替代原来的复杂清理逻辑 + [self.mediaManager cleanupResources]; + + // 重置UI状态 + self.displayModel = nil; + self.currentItemVo = nil; + self.titleLabel.text = @""; + self.subLabel.text = @""; + [self.levelIndicatorView resetToLevel:0]; +} + +- (void)updateCell:(MedalSeriesVo *)model isForSquare:(BOOL)isSquare { + MedalSeriesItemVo *itemVos = [model.medalSeries xpSafeObjectAtIndex:0]; + self.currentItemVo = itemVos; + + // 设置等级指示器 + self.levelIndicatorView.indicatorType = MedalsLevelIndicatorTypeNormal; + [self.levelIndicatorView setupWithMaxLevel:itemVos.medalLevel]; + + if (isSquare) { + self.displayModel = [itemVos.medalVos xpSafeObjectAtIndex:itemVos.medalVos.count - 1]; + [self.levelIndicatorView setSelectedLevel:itemVos.medalLevel animated:NO]; + } else { + self.displayModel = [itemVos.medalVos xpSafeObjectAtIndex:0]; + [self.levelIndicatorView setSelectedLevel:1 animated:NO]; + } + + self.levelIndicatorView.userInteractionEnabled = !isSquare; + + // 使用媒体管理器更新显示 - 替代原来的复杂逻辑 + [self.mediaManager updateDisplayWithModel:self.displayModel]; + + // 更新文本信息 + [self updateTextLabels]; +} + +- (void)updateTextLabels { + self.titleLabel.text = self.displayModel.name; + self.subLabel.text = [self.displayModel expireDateString]; +} + +#pragma mark - 可见性管理 - 大幅简化 + +- (void)willDisplay { + [self.mediaManager willDisplay]; +} + +- (void)didEndDisplaying { + [self.mediaManager didEndDisplaying]; +} + +#pragma mark - MedalMediaDisplayDelegate - 核心代理方法 + +- (NSString *)getMP4UrlFromModel:(id)model { + MedalVo *medalVo = (MedalVo *)model; + return medalVo.mp4Url; +} + +- (NSString *)getPicUrlFromModel:(id)model { + MedalVo *medalVo = (MedalVo *)model; + return medalVo.picUrl; +} + +- (NetImageView *)getImageView { + return self.imageView; +} + +- (VAPView *)getMP4View { + return self.mp4View; +} + +- (void)onMediaDisplayUpdated:(BOOL)isMP4 success:(BOOL)success { + // 可选:处理媒体显示状态更新 + NSLog(@"Media display updated: %@ - %@", isMP4 ? @"MP4" : @"Image", success ? @"Success" : @"Failed"); +} + +- (UIImage *)getDefaultPlaceholderImage { + return [UIImageConstant defaultEmptyPlaceholder]; +} + +#pragma mark - 生命周期 + +- (void)dealloc { + // 媒体管理器会自动处理资源清理 + NSLog(@"MedalsCollectionViewCell dealloc"); +} + +@end + +/* +重构效果对比: + +原始代码:~528行 +重构后代码:~150行(减少70%+) + +移除的重复代码: +- 所有媒体路径属性和管理逻辑 +- MP4播放器相关属性和方法 +- 复杂的播放状态管理 +- 应用生命周期通知处理 +- MP4解析和播放控制逻辑 +- 失败降级处理逻辑 +- 可见性管理的复杂逻辑 + +保留的业务逻辑: +- UI布局和样式 +- 等级指示器相关逻辑 +- 业务数据模型处理 +- 特定的UI更新逻辑 + +核心优势: +1. 代码量大幅减少,可读性提升 +2. 媒体处理逻辑统一管理 +3. Bug修复只需要在一个地方 +4. 新功能添加更容易 +5. 测试和维护成本降低 +*/ \ No newline at end of file diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsCyclePagerCell.m b/YuMi/Modules/YMMine/View/Medals/MedalsCyclePagerCell.m index 4061323e..4f20943b 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsCyclePagerCell.m +++ b/YuMi/Modules/YMMine/View/Medals/MedalsCyclePagerCell.m @@ -91,9 +91,12 @@ // 停止播放 [self stopMP4Playback]; - // 隐藏 mp4 视图 + // 更彻底地重置 mp4View + [self resetMP4View]; + + // 隐藏视图 self.mp4View.hidden = YES; - self.imageView.hidden = NO; + self.imageView.hidden = YES; // 重置状态 self.mp4Path = nil; @@ -179,10 +182,15 @@ } - (void)setMp4Path:(NSString *)mp4Path { - // 如果是相同的 mp4 路径,不需要重新加载 - if ([_mp4Path isEqualToString:mp4Path]) { + if ([NSString isEmpty:mp4Path]) { + self.mp4View.hidden = YES; + [self handleMP4FailureWithFallback]; return; } + // 如果是相同的 mp4 路径,不需要重新加载 +// if ([_mp4Path isEqualToString:mp4Path]) { +// return; +// } // 停止之前的 mp4 播放 [self stopMP4Playback]; @@ -239,6 +247,35 @@ #pragma mark - MP4 播放控制 +/// 重置 MP4 视图,清理所有缓存内容 +- (void)resetMP4View { + if (self.mp4View) { + // 方案1: 基础重置(推荐) + [self.mp4View stopHWDMP4]; + self.mp4View.tag = 0; + self.mp4View.hidden = YES; + + // 方案2: 完全重置(如果遇到严重的缓存问题才启用) + if (NO) { // 根据需要设置为 NO 来禁用完全重置 + [self.mp4View removeFromSuperview]; + _mp4View = nil; + // 重新添加 mp4View + [self.contentView addSubview:self.mp4View]; + [self.mp4View mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(self.imageView); + }]; + } + + // 方案3: 强制清理内存(备用方案) + // 通过设置新的frame来触发内部清理 + if (NO) { // 可以启用此方案作为替代 + CGRect currentFrame = self.mp4View.frame; + self.mp4View.frame = CGRectZero; + self.mp4View.frame = currentFrame; + } + } +} + - (void)stopMP4Playback { if (self.mp4View) { [self.mp4View stopHWDMP4]; @@ -246,44 +283,33 @@ } } -- (void)pauseMP4Playback { - if (self.mp4View && !self.mp4View.hidden) { - [self.mp4View pauseHWDMP4]; - } -} - -- (void)resumeMP4Playback { - if (self.mp4View && !self.mp4View.hidden && self.mp4Path) { - if (self.mp4View.tag == 1) { // 已准备好但尚未播放 - @kWeakify(self); - [self.mp4Parser parseWithURL:self.mp4Path - completionBlock:^(NSString * _Nullable videoUrl) { - @kStrongify(self); - if (![NSString isEmpty:videoUrl] && self.isVisible) { - [self startMP4PlaybackWithURL:videoUrl]; - } - } failureBlock:^(NSError * _Nullable error) { - @kStrongify(self); - // MP4 恢复播放失败,降级使用 picURL - [self handleMP4FailureWithFallback]; - }]; - } else { - [self.mp4View resumeHWDMP4]; - } - } -} - #pragma mark - 可见性管理 - (void)willDisplay { self.isVisible = YES; - // 如果有准备好的 MP4,立即开始播放 - if (self.mp4View.tag == 1 && !self.mp4View.hidden && self.mp4Path) { - [self resumeMP4Playback]; - } else if (!self.mp4View.hidden) { - // 恢复之前暂停的播放 - [self resumeMP4Playback]; + // 重新开始播放,而不是恢复暂停的播放 + if (!self.mp4View.hidden && self.mp4Path) { + // 彻底停止当前播放 + [self stopMP4Playback]; + + // 重新开始播放 + if (!_mp4Parser) { + self.mp4Parser = [[XPRoomGiftAnimationParser alloc] init]; + } + + @kWeakify(self); + [self.mp4Parser parseWithURL:self.mp4Path + completionBlock:^(NSString * _Nullable videoUrl) { + @kStrongify(self); + if (![NSString isEmpty:videoUrl] && self.isVisible) { + [self startMP4PlaybackWithURL:videoUrl]; + } + } failureBlock:^(NSError * _Nullable error) { + @kStrongify(self); + NSLog(@"[MedalsCyclePagerCell] Failed to restart mp4 in willDisplay: %@", error); + [self handleMP4FailureWithFallback]; + }]; } NSLog(@"[MedalsCyclePagerCell] willDisplay - isVisible: %@, mp4Path: %@", @@ -292,18 +318,34 @@ - (void)didEndDisplaying { self.isVisible = NO; - [self pauseMP4Playback]; + // 彻底停止播放,而不是暂停 + [self stopMP4Playback]; } #pragma mark - 通知处理 - (void)appDidEnterBackground { - [self pauseMP4Playback]; + [self stopMP4Playback]; } - (void)appWillEnterForeground { - if (self.isVisible) { - [self resumeMP4Playback]; + if (self.isVisible && !self.mp4View.hidden && self.mp4Path) { + // 重新开始播放 + if (!_mp4Parser) { + self.mp4Parser = [[XPRoomGiftAnimationParser alloc] init]; + } + + @kWeakify(self); + [self.mp4Parser parseWithURL:self.mp4Path + completionBlock:^(NSString * _Nullable videoUrl) { + @kStrongify(self); + if (![NSString isEmpty:videoUrl] && self.isVisible) { + [self startMP4PlaybackWithURL:videoUrl]; + } + } failureBlock:^(NSError * _Nullable error) { + @kStrongify(self); + [self handleMP4FailureWithFallback]; + }]; } } @@ -334,9 +376,7 @@ if (!_mp4View) { _mp4View = [[VAPView alloc] init]; _mp4View.contentMode = UIViewContentModeScaleAspectFit; -#if DEBUG - _mp4View.backgroundColor = [UIColor redColor]; -#endif +// [_mp4View enableOldVersion:YES]; } return _mp4View; } diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsDetailView.h b/YuMi/Modules/YMMine/View/Medals/MedalsDetailView.h index 1af3326f..ff254182 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsDetailView.h +++ b/YuMi/Modules/YMMine/View/Medals/MedalsDetailView.h @@ -6,12 +6,12 @@ // #import -@class MedalSeriesVo; +@class MedalSeriesItemVo; NS_ASSUME_NONNULL_BEGIN @interface MedalsDetailView : UIView -@property (nonatomic, strong) MedalSeriesVo *detailItemVo; +@property (nonatomic, strong) MedalSeriesItemVo *detailItemVo; @end diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsDetailView.m b/YuMi/Modules/YMMine/View/Medals/MedalsDetailView.m index 158a3757..0da10050 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsDetailView.m +++ b/YuMi/Modules/YMMine/View/Medals/MedalsDetailView.m @@ -72,6 +72,7 @@ make.centerX.mas_equalTo(self); make.top.mas_equalTo(self.imageView.mas_bottom).offset(8); make.leading.trailing.mas_equalTo(self).inset(13); + make.height.mas_equalTo(28); }]; [self addSubview:self.subLabel]; [self.subLabel mas_makeConstraints:^(MASConstraintMaker *make) { @@ -85,7 +86,7 @@ [self addSubview:self.levelIndicatorView]; [self.levelIndicatorView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self); - make.top.mas_equalTo(self.subLabel).offset(24); + make.top.mas_equalTo(self.subLabel.mas_bottom).offset(33); make.leading.trailing.mas_greaterThanOrEqualTo(self).inset(8); make.height.mas_equalTo(66); }]; @@ -116,27 +117,34 @@ // 添加点击手势到关闭按钮区域 UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundTap:)]; [closeButtonView addGestureRecognizer:tapGesture]; + + UIButton *back = [UIButton buttonWithType:UIButtonTypeCustom]; + [back setBackgroundImage:kImage(@"common_nav_back_white") forState:UIControlStateNormal]; + [back addTarget:selfWeak action:@selector(handleBackgroundTap:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:back]; + [back mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.mas_equalTo(16); + make.top.mas_equalTo(kStatusBarHeight); + make.size.mas_equalTo(CGSizeMake(22, 22)); + }]; } -- (void)setDetailItemVo:(MedalSeriesVo *)detailItemVo { - _detailItemVo = detailItemVo; - self.currentSeriesItemVO = [detailItemVo.medalSeries xpSafeObjectAtIndex:0]; - self.displayModel = [self.currentSeriesItemVO.medalVos xpSafeObjectAtIndex:0]; +- (void)setDetailItemVo:(MedalSeriesItemVo *)detailItemVo { +// _detailItemVo = detailItemVo; + self.currentSeriesItemVO = detailItemVo; + self.displayModel = [detailItemVo.medalVos xpSafeObjectAtIndex:0]; [self.levelIndicatorView setupWithMaxLevel:self.currentSeriesItemVO.medalLevel]; [self.levelIndicatorView setSelectedLevel:1 animated:NO]; - [self.levelIndicatorView setSeriesItems:detailItemVo.medalSeries]; + [self.levelIndicatorView setSeriesItems:detailItemVo.medalVos]; // 设置指示器类型为带图片 self.levelIndicatorView.indicatorType = MedalsLevelIndicatorTypeWithImage; // 为每个等级设置对应的图片,使用新的 URL 优先级逻辑 - for (NSInteger i = 0; i < self.currentSeriesItemVO.medalVos.count; i++) { - MedalVo *medalVo = [self.currentSeriesItemVO.medalVos xpSafeObjectAtIndex:i]; - if (medalVo) { - NSString *imageUrl = [self getImageUrlForMedal:medalVo]; - [self.levelIndicatorView setImageUrl:imageUrl forLevel:i + 1]; - } + for (MedalVo *medalVo in detailItemVo.medalVos) { + NSString *imageUrl = [self getImageUrlForMedal:medalVo]; + [self.levelIndicatorView setImageUrl:imageUrl forLevel:medalVo.level]; } [self updateDisplayWithCurrentModel]; diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.h b/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.h index ab75bb4b..530427ee 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.h +++ b/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.h @@ -6,7 +6,7 @@ // #import -@class MedalSeriesItemVo; +@class MedalSeriesItemVo, MedalVo; NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, MedalsLevelIndicatorType) { @@ -18,7 +18,7 @@ typedef NS_ENUM(NSInteger, MedalsLevelIndicatorType) { @property (nonatomic, copy) void (^levelSelectedBlock)(NSInteger level); @property (nonatomic, assign) MedalsLevelIndicatorType indicatorType; -@property (nonatomic, copy) NSArray *seriesItems; +@property (nonatomic, copy) NSArray *seriesItems; - (void)setupWithMaxLevel:(NSInteger)maxLevel; - (void)setSelectedLevel:(NSInteger)level animated:(BOOL)animated; @@ -40,7 +40,7 @@ typedef NS_ENUM(NSInteger, MedalsLevelIndicatorType) { * 设置系列数据,用于处理 MP4 和 PNG 资源 * @param seriesItems MedalSeriesItemVo 数组 */ -- (void)setSeriesItems:(NSArray *)seriesItems; +- (void)setSeriesItems:(NSArray *)seriesItems; @end diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.m b/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.m index 241627b7..8704ce4a 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.m +++ b/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.m @@ -610,25 +610,17 @@ } } -- (void)setSeriesItems:(NSArray *)seriesItems { +- (void)setSeriesItems:(NSArray *)seriesItems { _seriesItems = seriesItems; // 如果没有系列数据或者等级指示器还没有创建,直接返回 if (!seriesItems || seriesItems.count == 0 || _levelItems.count == 0) { return; } - - // 获取第一个系列项 - MedalSeriesItemVo *firstSeriesItem = [seriesItems xpSafeObjectAtIndex:0]; - if (!firstSeriesItem || !firstSeriesItem.medalVos) { - return; - } - - // 为每个等级设置对应的图片/MP4 - for (NSInteger i = 0; i < firstSeriesItem.medalVos.count && i < _levelItems.count; i++) { - MedalVo *medalVo = [firstSeriesItem.medalVos xpSafeObjectAtIndex:i]; + + for (MedalVo *medalVo in seriesItems) { if (medalVo && medalVo.picUrl) { - [self setImageUrl:medalVo.picUrl forLevel:i + 1]; + [self setImageUrl:medalVo.picUrl forLevel:medalVo.level]; } } } diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsRankViewController.m b/YuMi/Modules/YMMine/View/Medals/MedalsRankViewController.m index 3340c23d..985d21c0 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsRankViewController.m +++ b/YuMi/Modules/YMMine/View/Medals/MedalsRankViewController.m @@ -17,7 +17,7 @@ @property (nonatomic, strong) NetImageView *avatarImageView; @property (nonatomic, strong) UILabel *nameLabel; @property (nonatomic, strong) UILabel *countLabel; - +@property (nonatomic, strong) UILabel *detailLabel; + (void)registerTo:(UITableView *)tableView; + (instancetype)cellFor:(UITableView *)tableView atIndexPath:(NSIndexPath *)index; @@ -46,6 +46,7 @@ self.nameLabel.text = userModel.nick; self.indexLabel.text = @(userModel.rank).stringValue; self.countLabel.text = @(userModel.medalCount).stringValue; +// self.detailLabel.hidden = userModel.uid == 0; } - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { @@ -80,12 +81,12 @@ make.top.mas_equalTo(self.avatarImageView); }]; - UIImageView *rankIcon = [[UIImageView alloc] initWithImage:kImage(@"medals_rank")]; + UIImageView *rankIcon = [[UIImageView alloc] initWithImage:[kImage(@"medals_icon_rank") ms_SetImageForRTL]]; [self.contentView addSubview:rankIcon]; [rankIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.avatarImageView.mas_trailing).offset(6); make.bottom.mas_equalTo(self.avatarImageView); - make.size.mas_equalTo(CGSizeMake(10, 14)); + make.size.mas_equalTo(CGSizeMake(15, 15)); }]; self.countLabel = [UILabel labelInitWithText:@"" font:kFontMedium(14) textColor:[UIColor whiteColor]]; @@ -95,11 +96,11 @@ make.centerY.mas_equalTo(rankIcon); }]; - UILabel *detailLabel = [UILabel labelInitWithText:YMLocalizedString(@"20.20.61_text_13") + self.detailLabel = [UILabel labelInitWithText:YMLocalizedString(@"20.20.61_text_13") font:kFontRegular(12) textColor:[UIColor colorWithWhite:1 alpha:0.6]]; - [self.contentView addSubview:detailLabel]; - [detailLabel mas_makeConstraints:^(MASConstraintMaker *make) { + [self.contentView addSubview:self.detailLabel]; + [self.detailLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.trailing.mas_equalTo(self.contentView).offset(-28); make.centerY.mas_equalTo(self.contentView); }]; @@ -175,13 +176,16 @@ nameLabel = [UILabel labelInitWithText:@"" font:kFontSemibold(15) textColor:UIColorFromRGB(0xffffff)]; [self addSubview:nameLabel]; - medalIconImageView = [[UIImageView alloc] initWithImage:kImage(@"medals_rank")]; + medalIconImageView = [[UIImageView alloc] initWithImage:kImage(@"medals_icon_rank")]; medalCountLabel = [UILabel labelInitWithText:@"0" font:kFontMedium(14) textColor:UIColorFromRGB(0xffffff)]; medalCountStack = [[UIStackView alloc] initWithArrangedSubviews:@[ medalIconImageView, medalCountLabel ]]; [self addSubview:medalCountStack]; + [medalIconImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.size.mas_equalTo(CGSizeMake(15, 15)); + }]; honorLabel.hidden = YES; honorImageView.hidden = YES; @@ -349,7 +353,7 @@ - (void)setupRankList { [self.view addSubview:self.rankTableView]; - CGFloat height = 84 + kSafeAreaBottomHeight; + CGFloat height = 84; [self.rankTableView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(self.topTwo.mas_bottom).offset(40); make.bottom.mas_equalTo(self.view).offset(-height); @@ -421,6 +425,8 @@ self.countLabel.text = @(self.mineRankModel.medalCount).stringValue; self.rankList = [NSMutableArray array]; + NSInteger currentRank = 4; // 从第4名开始计算(前三名已经处理) + for (MedalsRankUserModel *user in model.rankList) { if (user.rank == 1) { self.topOne.userModel = user; @@ -430,8 +436,23 @@ self.topThree.userModel = user; } else { [self.rankList addObject:user]; + currentRank = MAX(currentRank, user.rank + 1); } } + + // 补充数据至 10 个(rankList 中应有 7 个,因为前三名在 top 视图中) + NSInteger targetCount = 7; // 总共10个,减去前3名 + while (self.rankList.count < targetCount) { + MedalsRankUserModel *emptyUser = [[MedalsRankUserModel alloc] init]; + emptyUser.rank = currentRank; + emptyUser.uid = 0; + emptyUser.avatar = @""; + emptyUser.nick = @"--"; + emptyUser.medalCount = 0; + [self.rankList addObject:emptyUser]; + currentRank++; + } + [self.rankTableView reloadData]; } } @@ -461,11 +482,15 @@ [tableView deselectRowAtIndexPath:indexPath animated:YES]; MedalsRankUserModel *model = [self.rankList xpSafeObjectAtIndex:indexPath.row]; - [self handleTopViewTap:model]; + + // 检查是否为有效的用户数据(非空补充数据) + if (model && model.uid != 0) { + [self handleTopViewTap:model]; + } } - (void)handleTopViewTap:(MedalsRankUserModel *)model { - if (model) { + if (model && model.uid != 0) { UserInfoModel *userInfo = [[UserInfoModel alloc] init]; userInfo.uid = model.uid; userInfo.avatar = model.avatar; diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsViewController.m b/YuMi/Modules/YMMine/View/Medals/MedalsViewController.m index 7fb7c5b4..3e4a302e 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsViewController.m +++ b/YuMi/Modules/YMMine/View/Medals/MedalsViewController.m @@ -38,10 +38,11 @@ typedef enum : NSInteger { @property (nonatomic, strong) TYCyclePagerView *medalsCyclePagerView; @property (nonatomic, strong) UIView *emptyView; @property (nonatomic, copy) UICollectionView *medalsCollectionView; +@property (nonatomic, strong) UIStackView *centerTabsStackView; -@property (nonatomic, strong) NSMutableArray *datasourceTaskMedals; -@property (nonatomic, strong) NSMutableArray *datasourceActivityMedals; -@property (nonatomic, strong) NSMutableArray *datasourceGloryMedals; +@property (nonatomic, strong) NSMutableArray *datasourceTaskMedals; +@property (nonatomic, strong) NSMutableArray *datasourceActivityMedals; +@property (nonatomic, strong) NSMutableArray *datasourceGloryMedals; @property (nonatomic, copy) NSArray *useMedals; @property (nonatomic, assign) NSInteger currentPageTaskMedals; @@ -52,7 +53,9 @@ typedef enum : NSInteger { @property (nonatomic, assign) MedalsCenterDisplayType displayType; @property (nonatomic, strong) UserMedalsModel *userMedalsModel; -@property (nonatomic, copy) NSMutableArray *squareMedalVo; +@property (nonatomic, copy) NSMutableArray *taskSquareMedalVo; +@property (nonatomic, copy) NSMutableArray *activitySquareMedalVo; +@property (nonatomic, copy) NSMutableArray *glorySquareMedalVo; @property (nonatomic, strong) UIImageView *otherBG; @property (nonatomic, strong) NetImageView *otherAvatar; @@ -238,7 +241,7 @@ typedef enum : NSInteger { [self setupNavigationBar]; - if (self.displayType != MedalsCenterDisplayType_Other) { + if (self.displayType == MedalsCenterDisplayType_Mine) { [self setupWearingButton]; } } @@ -322,12 +325,12 @@ typedef enum : NSInteger { make.top.mas_equalTo(self.otherAvatar); }]; - UIImageView *rankIcon = [[UIImageView alloc] initWithImage:kImage(@"medals_rank")]; + UIImageView *rankIcon = [[UIImageView alloc] initWithImage:kImage(@"medals_icon_other")]; [self.view addSubview:rankIcon]; [rankIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.otherAvatar.mas_trailing).offset(10); make.bottom.mas_equalTo(self.otherAvatar); - make.size.mas_equalTo(CGSizeMake(14, 18)); + make.size.mas_equalTo(CGSizeMake(22, 22)); }]; self.otherCountLabel = [UILabel labelInitWithText:@"" @@ -339,12 +342,12 @@ typedef enum : NSInteger { make.centerY.mas_equalTo(rankIcon); }]; - self.otherMedal = [[NetImageView alloc] initWithImage:kImage(@"medals_empty")]; + self.otherMedal = [[NetImageView alloc] initWithImage:kImage(@"medals_empty_other")]; [self.view addSubview:self.otherMedal]; [self.otherMedal mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(bg); make.trailing.mas_equalTo(bg).offset(-15); - make.size.mas_equalTo(CGSizeMake(80, 80)); + make.size.mas_equalTo(CGSizeMake(81, 81)); }]; [self.view addSubview:self.otherMP4View]; @@ -356,9 +359,9 @@ typedef enum : NSInteger { } - (void)setupCenterTabs { - UIStackView *centerTabsStackView = [self centerTabStack]; - [self.view addSubview:centerTabsStackView]; - [centerTabsStackView mas_makeConstraints:^(MASConstraintMaker *make) { + self.centerTabsStackView = [self centerTabStack]; + [self.view addSubview:self.centerTabsStackView]; + [self.centerTabsStackView mas_makeConstraints:^(MASConstraintMaker *make) { if (self.displayType == MedalsCenterDisplayType_Other) { make.top.mas_equalTo(self.otherBG.mas_bottom).offset(18); } else { @@ -384,10 +387,11 @@ typedef enum : NSInteger { [wearingButton mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(90); make.trailing.mas_equalTo(-12); + make.height.mas_equalTo(25); }]; [wearingBg mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.mas_equalTo(wearingButton).insets(UIEdgeInsetsMake(0, -15, 0, -25)); + make.edges.mas_equalTo(wearingButton).insets(UIEdgeInsetsMake(0, -25, 0, -25)); }]; } @@ -445,11 +449,7 @@ typedef enum : NSInteger { [self.medalsCollectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.bottom.mas_equalTo(self.view); make.trailing.leading.mas_equalTo(self.view).inset(26); - if (self.displayType == MedalsCenterDisplayType_Other) { - make.height.mas_equalTo(kGetScaleWidth(500)); - } else { - make.height.mas_equalTo(kGetScaleWidth(380)); - } + make.top.mas_equalTo(self.centerTabsStackView.mas_bottom).offset(68); }]; // 添加下拉刷新 @@ -583,6 +583,32 @@ typedef enum : NSInteger { } - (void)squareMedalsSuccess:(NSArray *)squareMedalsModel { + switch (self.currentTabType) { + case MedalsCenterTab_TaskMedals: + if (self.currentPageTaskMedals == 1) { + self.taskSquareMedalVo = squareMedalsModel.mutableCopy; + } else { + [self.taskSquareMedalVo addObjectsFromArray:squareMedalsModel]; + } + break; + case MedalsCenterTab_ActivityMedals: + if (self.currentPageActivityMedals == 1) { + self.activitySquareMedalVo = squareMedalsModel.mutableCopy; + } else { + [self.activitySquareMedalVo addObjectsFromArray:squareMedalsModel]; + } + break; + case MedalsCenterTab_GloryMedals: + if (self.currentPageGloryMedals == 1) { + self.glorySquareMedalVo = squareMedalsModel.mutableCopy; + } else { + [self.glorySquareMedalVo addObjectsFromArray:squareMedalsModel]; + } + break; + default: + break; + } + [self endReresh]; [self _updateDataSource:squareMedalsModel]; [self _updateWearingInfo]; @@ -622,24 +648,25 @@ typedef enum : NSInteger { NSMutableArray *editArr = [NSMutableArray array]; switch (self.currentTabType) { case MedalsCenterTab_TaskMedals: - arr = self.datasourceTaskMedals.copy; + arr = self.taskSquareMedalVo.copy; break; case MedalsCenterTab_ActivityMedals: - arr = self.datasourceActivityMedals.copy; + arr = self.activitySquareMedalVo.copy; break; case MedalsCenterTab_GloryMedals: - arr = self.datasourceGloryMedals.copy; + arr = self.glorySquareMedalVo.copy; break; default: break; } for (MedalSeriesVo *seriesVO in arr) { - MedalSeriesItemVo *item = [seriesVO.medalSeries xpSafeObjectAtIndex:0]; - if (item) { - MedalVo *medal = [item.medalVos xpSafeObjectAtIndex:0]; - if (medal) { - [editArr addObject:medal]; + for (MedalSeriesItemVo *item in seriesVO.medalSeries) { + // 遍历所有medalSeries,找到最高级别的 + for (MedalVo *medalVo in item.medalVos) { + if (medalVo.level == item.medalLevel) { + [editArr addObject:medalVo]; + } } } } @@ -651,7 +678,7 @@ typedef enum : NSInteger { self.emptyUserMedalButton.hidden = YES; self.medalsCyclePagerView.hidden = NO; [self.medalsCyclePagerView reloadData]; - if (self.useMedals.count > 1 && self.displayType == MedalsCenterDisplayType_Square) { + if (self.useMedals.count > 1 && self.displayType == MedalsCenterDisplayType_Square) { // 启动自动轮播,从第一个位置开始 [self startAutoScroll:0]; @@ -722,7 +749,7 @@ typedef enum : NSInteger { self.otherMedal.imageUrl = imageUrl; } else { // 显示默认空勋章图片 - self.otherMedal.image = kImage(@"medals_empty"); + self.otherMedal.image = kImage(@"medals_empty_other"); } } @@ -782,29 +809,34 @@ typedef enum : NSInteger { [self.medalsCollectionView.mj_footer resetNoMoreData]; } + NSMutableArray *itemDataSources = [NSMutableArray array]; + for (MedalSeriesVo *vo in models) { + [itemDataSources addObjectsFromArray:vo.medalSeries]; + } + switch (self.currentTabType) { case MedalsCenterTab_TaskMedals: if (self.currentPageTaskMedals == 1) { self.emptyView.hidden = (models.count != 0); - self.datasourceTaskMedals = models.mutableCopy; + self.datasourceTaskMedals = itemDataSources; } else { - [self.datasourceTaskMedals addObjectsFromArray:models]; + [self.datasourceTaskMedals addObjectsFromArray:itemDataSources]; } break; case MedalsCenterTab_ActivityMedals: if (self.currentPageActivityMedals == 1) { self.emptyView.hidden = (models.count != 0); - self.datasourceActivityMedals = models.mutableCopy; + self.datasourceActivityMedals = itemDataSources; } else { - [self.datasourceActivityMedals addObjectsFromArray:models]; + [self.datasourceActivityMedals addObjectsFromArray:itemDataSources]; } break; case MedalsCenterTab_GloryMedals: if (self.currentPageGloryMedals == 1) { self.emptyView.hidden = (models.count != 0); - self.datasourceGloryMedals = models.mutableCopy; + self.datasourceGloryMedals = itemDataSources; } else { - [self.datasourceGloryMedals addObjectsFromArray:models]; + [self.datasourceGloryMedals addObjectsFromArray:itemDataSources]; } break; @@ -837,6 +869,7 @@ typedef enum : NSInteger { - (void)didTapWearingButton:(UIButton *)sender { MedalsWearingViewController *vc = [[MedalsWearingViewController alloc] init]; + vc.userInfo = self.userInfo; // 设置数据变化回调 @kWeakify(self); @@ -852,6 +885,7 @@ typedef enum : NSInteger { - (void)didTapEmptyMedalButton:(UIButton *)sender { MedalsWearingViewController *vc = [[MedalsWearingViewController alloc] init]; + vc.userInfo = self.userInfo; // 设置数据变化回调 @kWeakify(self); @@ -900,7 +934,7 @@ typedef enum : NSInteger { - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { MedalsCollectionViewCell *cell = [MedalsCollectionViewCell cellFor:collectionView atIndexPath:indexPath]; - [cell updateCell:[self loadModel:indexPath.item]]; + [cell updateCell:[self loadModel:indexPath.item] isForSquare:self.displayType == MedalsCenterDisplayType_Square]; return cell; } @@ -930,8 +964,8 @@ typedef enum : NSInteger { [self.view addSubview:view]; } -- (MedalSeriesVo *)loadModel:(NSInteger)row { - MedalSeriesVo *model = nil; +- (MedalSeriesItemVo *)loadModel:(NSInteger)row { + MedalSeriesItemVo *model = nil; switch (self.currentTabType) { case MedalsCenterTab_TaskMedals: model = [self.datasourceTaskMedals xpSafeObjectAtIndex:row]; @@ -948,6 +982,25 @@ typedef enum : NSInteger { return model; } +- (MedalSeriesVo *)loadDetailModel:(NSInteger)row { + MedalSeriesVo *model = nil; + switch (self.currentTabType) { + case MedalsCenterTab_TaskMedals: + model = [self.taskSquareMedalVo xpSafeObjectAtIndex:row]; + break; + case MedalsCenterTab_ActivityMedals: + model = [self.activitySquareMedalVo xpSafeObjectAtIndex:row]; + break; + case MedalsCenterTab_GloryMedals: + model = [self.glorySquareMedalVo xpSafeObjectAtIndex:row]; + break; + default: + break; + } + return model; +} + + #pragma mark - TYCyclePagerView DataSource & Delegate - (NSInteger)numberOfItemsInPagerView:(TYCyclePagerView *)pageView { @@ -980,8 +1033,12 @@ typedef enum : NSInteger { [self startAutoScroll:toIndex]; } - MedalVo *vo = [self.useMedals xpSafeObjectAtIndex:toIndex]; - self.medalDescLabel.text = [vo expireDateString]; + if (self.displayType == MedalsCenterDisplayType_Mine) { + MedalVo *vo = [self.useMedals xpSafeObjectAtIndex:toIndex]; + self.medalDescLabel.text = [vo expireDateString]; + } else { + self.medalDescLabel.text = @""; + } } #pragma mark - Lazy load @@ -1115,7 +1172,12 @@ typedef enum : NSInteger { _emptyView = [[UIView alloc] init]; _emptyView.backgroundColor = [UIColor clearColor]; - UILabel *titleLabel = [UILabel labelInitWithText:YMLocalizedString(@"20.20.61_text_7") + NSString *content = YMLocalizedString(@"20.20.61_text_7.1"); + if (self.displayType == MedalsCenterDisplayType_Mine) { + content = YMLocalizedString(@"20.20.61_text_7.2"); + } + + UILabel *titleLabel = [UILabel labelInitWithText:content font:kFontRegular(14) textColor:UIColorFromRGB(0xafb1b3)]; titleLabel.numberOfLines = 2; diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsWearingListCollectionViewCell.m b/YuMi/Modules/YMMine/View/Medals/MedalsWearingListCollectionViewCell.m index 9e067359..e6978af3 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsWearingListCollectionViewCell.m +++ b/YuMi/Modules/YMMine/View/Medals/MedalsWearingListCollectionViewCell.m @@ -186,9 +186,9 @@ - (void)setMp4Path:(NSString *)mp4Path { // 如果是相同的 mp4 路径,不需要重新加载 - if ([_mp4Path isEqualToString:mp4Path]) { - return; - } +// if ([_mp4Path isEqualToString:mp4Path]) { +// return; +// } // 停止之前的 mp4 播放 [self stopMP4Playback]; diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsWearingViewController.h b/YuMi/Modules/YMMine/View/Medals/MedalsWearingViewController.h index 831e6005..477cdb89 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsWearingViewController.h +++ b/YuMi/Modules/YMMine/View/Medals/MedalsWearingViewController.h @@ -6,13 +6,14 @@ // #import "MvpViewController.h" - +@class UserInfoModel; NS_ASSUME_NONNULL_BEGIN @interface MedalsWearingViewController : MvpViewController /// 数据变化回调,当佩戴数据发生改变时触发 @property (nonatomic, copy) void (^dataChangedCallback)(void); +@property (nonatomic, strong) UserInfoModel *userInfo; @end diff --git a/YuMi/Modules/YMMine/View/Medals/MedalsWearingViewController.m b/YuMi/Modules/YMMine/View/Medals/MedalsWearingViewController.m index 60858567..0f5e9491 100644 --- a/YuMi/Modules/YMMine/View/Medals/MedalsWearingViewController.m +++ b/YuMi/Modules/YMMine/View/Medals/MedalsWearingViewController.m @@ -10,6 +10,8 @@ #import "MedalsWearingListCollectionViewCell.h" #import "MedalsWearingControlCollectionViewCell.h" #import "MJRefresh.h" +#import "UserInfoModel.h" +#import "XPWebViewController.h" @interface MedalsWearingViewController () @@ -221,9 +223,87 @@ } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - MedalVo *vo = [self.allMedalsVo xpSafeObjectAtIndex:indexPath.row]; - if (vo) { - [self.presenter updateMedalUseStatus:vo.id isUse:!vo.useStatus]; + if (collectionView == self.controlAreaCollectionView) { + // 当前 cell 的座位索引(从1开始) + NSInteger currentSeatIndex = indexPath.row + 1; + NSString *seatKey = [NSString stringWithFormat:@"%ld", (long)currentSeatIndex]; + NSArray *vipLevels = self.vipSeatDic[seatKey]; + + // 计算当前位置显示的VIP level + NSInteger displayedVipLevel = -1; + + // 找到 vipSeatDic 的最小 key + NSInteger minSeatIndex = NSIntegerMax; + for (NSString *key in self.vipSeatDic.allKeys) { + NSInteger seatIndex = [key integerValue]; + if (seatIndex < minSeatIndex) { + minSeatIndex = seatIndex; + } + } + + // 判断当前位置的VIP level + if (minSeatIndex != NSIntegerMax && currentSeatIndex >= minSeatIndex) { + if (vipLevels && vipLevels.count > 0) { + // 找到最低的 VIP level + NSInteger currentMinVipLevel = NSIntegerMax; + for (NSNumber *levelNum in vipLevels) { + NSInteger level = [levelNum integerValue]; + if (level < currentMinVipLevel) { + currentMinVipLevel = level; + } + } + if (currentMinVipLevel != NSIntegerMax) { + displayedVipLevel = currentMinVipLevel; + } + } else if (self.minVipLevelForSeats > 0) { + displayedVipLevel = self.minVipLevelForSeats; + } + } + + UserVipInfoVo *vipVo = self.userInfo.userVipInfoVO; + if (vipVo) { + if (vipVo.vipLevel < displayedVipLevel) { + TTAlertConfig * config = [[TTAlertConfig alloc] init]; +// config.actionStyle = 0; + config.title = YMLocalizedString(@"20.20.61_text_17"); + config.message = YMLocalizedString(@"20.20.61_text_15"); + + TTAlertButtonConfig *confirmCongif = config.confirmButtonConfig; + confirmCongif.title = YMLocalizedString(@"20.20.61_text_18"); + config.confirmButtonConfig = confirmCongif; + @kWeakify(self); + [TTPopup alertWithConfig:config confirmHandler:^{ + @kStrongify(self); + XPWebViewController * webVC =[[XPWebViewController alloc] initWithRoomUID:nil]; + webVC.url = URLWithType(kVIP); + [self.navigationController pushViewController:webVC animated:YES]; + } cancelHandler:^{ + }]; + } + } + + NSLog(@"[MedalsWearing] 点击位置: %ld, 座位索引: %ld, 显示VIP等级: %ld, VIP数据: %@, 最小座位索引: %ld, 全局最低VIP: %ld", + (long)indexPath.row, + (long)currentSeatIndex, + (long)displayedVipLevel, + vipLevels ?: @"无", + (long)minSeatIndex, + (long)self.minVipLevelForSeats); + } else if (collectionView == self.medalsAreaCollectionView) { + if (self.useMedalsVo.count >= 10) { + TTAlertConfig * config = [[TTAlertConfig alloc] init]; + config.actionStyle = 0; + config.title = YMLocalizedString(@"UserDetail_CP_Toast_0"); + config.message = YMLocalizedString(@"20.20.61_text_16"); + [TTPopup alertWithConfig:config confirmHandler:^{ + } cancelHandler:^{ + }]; + } else { + MedalVo *vo = [self.allMedalsVo xpSafeObjectAtIndex:indexPath.row]; + if (vo) { + [self.presenter updateMedalUseStatus:vo.id isUse:!vo.useStatus]; + } + } } } diff --git a/YuMi/Modules/YMMine/View/MineInfo/XPMineUserInfoViewController.m b/YuMi/Modules/YMMine/View/MineInfo/XPMineUserInfoViewController.m index 08bcffd6..1dcb58c1 100644 --- a/YuMi/Modules/YMMine/View/MineInfo/XPMineUserInfoViewController.m +++ b/YuMi/Modules/YMMine/View/MineInfo/XPMineUserInfoViewController.m @@ -388,7 +388,10 @@ HWDMP4PlayDelegate> self.userInfo.relationUserVO = userInfo.relationUserVO; self.headView.relationUser = userInfo.relationUserVO; + + userInfo.medalsPic = self.userInfo.medalsPic; self.headerHeight = [XPMineUserInfoHeaderView headerHeight:userInfo cps:0]; + [self.pagingView reloadData]; ///上传访问记录 diff --git a/YuMi/Modules/YMMine/View/Recharge/XPIAPRechargeCollectionViewCell.m b/YuMi/Modules/YMMine/View/Recharge/XPIAPRechargeCollectionViewCell.m index 0e49a0b1..d13d088b 100644 --- a/YuMi/Modules/YMMine/View/Recharge/XPIAPRechargeCollectionViewCell.m +++ b/YuMi/Modules/YMMine/View/Recharge/XPIAPRechargeCollectionViewCell.m @@ -86,16 +86,14 @@ - (void)setRechargeModel:(RechargeListModel *)rechargeModel { _rechargeModel = rechargeModel; NSMutableAttributedString *priceStr = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"$%.2f",rechargeModel.money.floatValue]]; -// [priceStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:8] range:NSMakeRange(0, 1)]; self.priceLabel.attributedText = priceStr; - NSCharacterSet* nonDigits =[[NSCharacterSet decimalDigitCharacterSet] invertedSet]; - int remainSecond = [[rechargeModel.prodName stringByTrimmingCharactersInSet:nonDigits] intValue]; - self.numLabel.text = [NSString stringWithFormat:@"%d",remainSecond]; + self.numLabel.text = [NSString stringWithFormat:@"%ld", + (long)rechargeModel.chargeGoldNum]; FirstRechargeModel *model = [FirstRechargeManager.sharedManager loadCurrentModel]; if (model && model.chargeStatus == NO) { for (FirstRechargeLevelModel *levelModel in model.levelCharge) { - if (levelModel.exp == remainSecond) { + if (levelModel.exp == rechargeModel.chargeGoldNum) { [self.moneyLabel updateContent: [NSString stringWithFormat:@"+ %@", @(levelModel.awardNum)]]; break; } diff --git a/YuMi/Modules/YMMine/View/SubViews/MineInfo/XPMineUserInfoHeaderView.m b/YuMi/Modules/YMMine/View/SubViews/MineInfo/XPMineUserInfoHeaderView.m index 32d66ec7..988c6c84 100644 --- a/YuMi/Modules/YMMine/View/SubViews/MineInfo/XPMineUserInfoHeaderView.m +++ b/YuMi/Modules/YMMine/View/SubViews/MineInfo/XPMineUserInfoHeaderView.m @@ -450,32 +450,12 @@ HWDMP4PlayDelegate> self.stack.alignment = UIStackViewAlignmentLeading; self.stack.spacing = 4; -// if (self.userInfo.guildNameplateIcon.length > 0) { -// scrollView.translatesAutoresizingMaskIntoConstraints = NO; -// [self.userInfoView addSubview:scrollView]; -// [scrollView mas_makeConstraints:^(MASConstraintMaker *make) { -// make.leading.mas_equalTo(kGetScaleWidth(15)); -// make.trailing.mas_equalTo(kGetScaleWidth(-15)); -// make.top.mas_equalTo(self.nickStackView.mas_bottom).offset(5); -// make.height.mas_equalTo(24); -// }]; -// [scrollView addSubview:self.stack]; -// [self.stack mas_makeConstraints:^(MASConstraintMaker *make) { -// make.edges.equalTo(scrollView); // 内容视图的边缘与滚动视图一致 -// make.height.equalTo(scrollView); // 高度与滚动视图一致,确保仅水平滚动 -// }]; -// -//// if (isMSRTL()) { -//// _idEmptyView = [[UIView alloc] init]; -//// } -// } else { - [self.userInfoView addSubview:self.stack]; - [self.stack mas_makeConstraints:^(MASConstraintMaker *make) { - make.leading.mas_equalTo(kGetScaleWidth(15)); - make.top.mas_equalTo(self.nickStackView.mas_bottom).offset(5); - make.height.mas_equalTo(24); - }]; -// } + [self.userInfoView addSubview:self.stack]; + [self.stack mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.mas_equalTo(kGetScaleWidth(15)); + make.top.mas_equalTo(self.nickStackView.mas_bottom).offset(5); + make.height.mas_equalTo(24); + }]; [self.stack addArrangedSubview:self.beautIDView]; [self.stack addArrangedSubview:self.idLabel]; @@ -543,14 +523,21 @@ HWDMP4PlayDelegate> make.top.mas_equalTo(self.nameplateCollectionView.mas_bottom).offset(6); make.leading.mas_equalTo(self.userInfoView).offset(kGetScaleWidth(15)); make.trailing.mas_equalTo(self.userInfoView).offset(kGetScaleWidth(-15)); - make.height.mas_equalTo(kGetScaleWidth(30)); + make.height.mas_equalTo(0); + }]; + + UIView *container = [[UIView alloc] init]; + container.backgroundColor = [UIColor clearColor]; + [self.medalsScrollView addSubview:container]; + [container mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(self.medalsScrollView); }]; // 在 ScrollView 中添加 StackView - [self.medalsScrollView addSubview:self.medalsStackView]; + [container addSubview:self.medalsStackView]; [self.medalsStackView mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.mas_equalTo(self.medalsScrollView); - make.height.mas_equalTo(self.medalsScrollView); + make.edges.mas_equalTo(container); + make.height.mas_equalTo(kGetScaleWidth(30)); }]; // 默认隐藏,有数据时再显示 @@ -1648,8 +1635,8 @@ HWDMP4PlayDelegate> _medalsStackView = [[UIStackView alloc] init]; _medalsStackView.backgroundColor = [UIColor clearColor]; _medalsStackView.axis = UILayoutConstraintAxisHorizontal; - _medalsStackView.distribution = UIStackViewDistributionEqualSpacing; - _medalsStackView.alignment = UIStackViewAlignmentCenter; + _medalsStackView.distribution = UIStackViewDistributionFill;//UIStackViewDistributionEqualSpacing; + _medalsStackView.alignment = UIStackViewAlignmentLeading; _medalsStackView.spacing = 5; } return _medalsStackView; @@ -1679,6 +1666,10 @@ HWDMP4PlayDelegate> return; } + [self.medalsScrollView mas_updateConstraints:^(MASConstraintMaker *make) { + make.height.mas_equalTo(kGetScaleWidth(30)); + }]; + self.medalsScrollView.hidden = NO; NSMutableArray *mp4Views = [NSMutableArray array]; @@ -1731,17 +1722,17 @@ HWDMP4PlayDelegate> [self.medalsStackView addArrangedSubview:medalContainer]; [medalContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.width.height - .mas_equalTo(kGetScaleWidth(30)) - .priority(UILayoutPriorityRequired); + .mas_equalTo(kGetScaleWidth(30)); +// .priority(UILayoutPriorityRequired); }]; - // 设置内容压缩阻力优先级,防止被压缩 - [medalContainer setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; - [medalContainer setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - - // 设置内容拥抱优先级,防止被拉伸 - [medalContainer setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; - [medalContainer setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; +// // 设置内容压缩阻力优先级,防止被压缩 +// [medalContainer setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; +// [medalContainer setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; +// +// // 设置内容拥抱优先级,防止被拉伸 +// [medalContainer setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; +// [medalContainer setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; } // 更新 ScrollView 的 contentSize @@ -1752,6 +1743,19 @@ HWDMP4PlayDelegate> CGFloat expectedWidth = count * kGetScaleWidth(30) + (count - 1) * 5; stackViewWidth = expectedWidth; } + + CGFloat targetWidth = KScreenWidth - kGetScaleWidth(40); + if (stackViewWidth < targetWidth) { + UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, targetWidth, kGetScaleWidth(30))]; + v.backgroundColor = [UIColor clearColor]; + [self.medalsStackView addArrangedSubview:v]; + [v mas_remakeConstraints:^(MASConstraintMaker *make) { + make.size + .mas_equalTo(CGSizeMake(targetWidth-stackViewWidth, kGetScaleWidth(30))); + }]; + stackViewWidth = targetWidth; + } + [self.medalsStackView layoutIfNeeded]; self.medalsScrollView.contentSize = CGSizeMake(stackViewWidth, kGetScaleWidth(30)); self.medalMP4Views = [mp4Views copy]; diff --git a/YuMi/Modules/YMMine/View/XPMineViewController.m b/YuMi/Modules/YMMine/View/XPMineViewController.m index a4bbf6bd..05004ddf 100644 --- a/YuMi/Modules/YMMine/View/XPMineViewController.m +++ b/YuMi/Modules/YMMine/View/XPMineViewController.m @@ -518,11 +518,11 @@ UIKIT_EXTERN NSString *kRequestTicket; } ///转赠钻石 -(void)pushGiveDiamondVC{ -// XPWebViewController * webVC =[[XPWebViewController alloc] initWithRoomUID:nil]; -// webVC.url = URLWithType(kTransfer); -// [self.navigationController pushViewController:webVC animated:YES]; - XPMineGiveDiamondVC *giveDiamondVC = [[XPMineGiveDiamondVC alloc] initWithUserModel:self.userInfo]; - [self.navigationController pushViewController:giveDiamondVC animated:YES]; + XPWebViewController * webVC =[[XPWebViewController alloc] initWithRoomUID:nil]; + webVC.url = URLWithType(kTransfer); + [self.navigationController pushViewController:webVC animated:YES]; +// XPMineGiveDiamondVC *giveDiamondVC = [[XPMineGiveDiamondVC alloc] initWithUserModel:self.userInfo]; +// [self.navigationController pushViewController:giveDiamondVC animated:YES]; } ///点击充值 -(void)pushThirdPartyPayVC{ diff --git a/YuMi/Modules/YMNewHome/View/XPHomePagingViewController.m b/YuMi/Modules/YMNewHome/View/XPHomePagingViewController.m index 80e96a3b..1abe5b7f 100644 --- a/YuMi/Modules/YMNewHome/View/XPHomePagingViewController.m +++ b/YuMi/Modules/YMNewHome/View/XPHomePagingViewController.m @@ -10,11 +10,16 @@ #import "XPNewHomeViewController.h" #import "XPHomeMineViewController.h" #import "XPRoomSearchContainerViewController.h" +#import "XPWebViewController.h" #import "Api+Gift.h" #import "XPGiftStorage.h" +#import "FirstRechargeManager.h" +#import "FirstRechargeModel.h" +#import "YUMIHtmlUrl.h" +#import "YUMIMacroUitls.h" -@interface XPHomePagingViewController () +@interface XPHomePagingViewController () @property (nonatomic, strong) UIView *topControlView; @property (nonatomic, strong) UIButton *mineButton; @@ -27,10 +32,20 @@ @property (nonatomic, strong) XPNewHomeViewController *recommendVC; @property (nonatomic, strong) XPHomeMineViewController *mineVC; +// 首充弹窗引用 +@property (nonatomic, strong) XPWebViewController *firstChargeWebVC; +// 首充弹窗背景视图 +@property (nonatomic, strong) UIView *firstChargeBackgroundView; + @end @implementation XPHomePagingViewController +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [FirstRechargeManager sharedManager].delegate = nil; +} + - (BOOL)isHiddenNavBar { return YES; } @@ -38,6 +53,18 @@ - (void)viewDidLoad { [super viewDidLoad]; [self setup]; + + // 注册首充管理器监听 + [FirstRechargeManager sharedManager].delegate = self; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + // 启动首充监控 + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [[FirstRechargeManager sharedManager] startMonitoring]; + }); } - (void)setup { @@ -193,4 +220,113 @@ [self displayRecommendTab]; } } + +- (void)tokenInvalid { + // 停止首充监控 + [[FirstRechargeManager sharedManager] stopMonitoring]; + +// [super tokenInvalid]; +} + +#pragma mark - FirstRechargeManagerDelegate + +- (void)firstRechargeManager:(FirstRechargeManager *)manager didCheckFirstRecharge:(FirstRechargeModel *)model shouldShow:(BOOL)shouldShow { + if (shouldShow && model.chargeStatus == NO) { + // 展示首充弹窗 + [self showFirstRechargePopup:model]; + + // 标记今天已展示过(在展示后标记) + [[FirstRechargeManager sharedManager] markTodayShown]; + } +} + +- (void)showFirstRechargePopup:(FirstRechargeModel *)model { + if (model.chargeStatus) { + return; + } + + // 如果已经有弹窗在显示,先移除它 + [self removeFirstChargePopup]; + + // 创建背景视图 + self.firstChargeBackgroundView = [[UIView alloc] initWithFrame:self.view.bounds]; + self.firstChargeBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; + self.firstChargeBackgroundView.alpha = 0; + + // 添加点击手势 + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundTap:)]; + [self.firstChargeBackgroundView addGestureRecognizer:tapGesture]; + + // 添加到当前视图 + [self.view addSubview:self.firstChargeBackgroundView]; + + // 创建并配置 web 视图 + self.firstChargeWebVC = [[XPWebViewController alloc] initWithRoomUID:@""]; + self.firstChargeWebVC.isPush = NO; + self.firstChargeWebVC.url = URLWithType(kFirstChargeHomeIndex); + + [self addChildViewController:self.firstChargeWebVC]; + [self.firstChargeBackgroundView addSubview:self.firstChargeWebVC.view]; + [self.firstChargeWebVC didMoveToParentViewController:self]; + + self.firstChargeWebVC.view.backgroundColor = [UIColor clearColor]; + self.firstChargeWebVC.webview.backgroundColor = [UIColor clearColor]; + self.firstChargeWebVC.webview.opaque = NO; + self.firstChargeWebVC.webview.scrollView.backgroundColor = [UIColor clearColor]; + self.firstChargeWebVC.view.frame = CGRectMake(0, + 0, + kGetScaleWidth(305), + kGetScaleWidth(403)); + self.firstChargeWebVC.view.center = self.firstChargeBackgroundView.center; + + @kWeakify(self); + [self.firstChargeWebVC setUrlLoadCompleted:^(BOOL result, NSError * _Nullable error) { + @kStrongify(self); + [UIView animateWithDuration:0.5 animations:^{ + self.firstChargeBackgroundView.alpha = 1; + }]; + }]; + [self.firstChargeWebVC setDidTapCharge:^{ + @kStrongify(self); + [self removeFirstChargePopup]; + }]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self removeFirstChargePopup]; + }); +} + +// 处理背景点击事件 +- (void)handleBackgroundTap:(UITapGestureRecognizer *)gesture { + // 获取点击位置 + CGPoint location = [gesture locationInView:self.firstChargeBackgroundView]; + + // 检查是否点击在 webView 区域外 + if (self.firstChargeWebVC && self.firstChargeWebVC.view) { + CGRect webViewFrame = self.firstChargeWebVC.view.frame; + if (!CGRectContainsPoint(webViewFrame, location)) { + // 点击在 webView 外部,移除弹窗 + [self removeFirstChargePopup]; + } + } else { + // 如果 webView 不存在,直接移除弹窗 + [self removeFirstChargePopup]; + } +} + +// 移除弹窗 +- (void)removeFirstChargePopup { + if (self.firstChargeWebVC) { + [self.firstChargeWebVC willMoveToParentViewController:nil]; + [self.firstChargeWebVC.view removeFromSuperview]; + [self.firstChargeWebVC removeFromParentViewController]; + self.firstChargeWebVC = nil; + } + + if (self.firstChargeBackgroundView) { + [self.firstChargeBackgroundView removeFromSuperview]; + self.firstChargeBackgroundView = nil; + } +} + @end diff --git a/YuMi/Modules/YMNewHome/View/XPNewHomeViewController.m b/YuMi/Modules/YMNewHome/View/XPNewHomeViewController.m index 2e532f76..817f565a 100644 --- a/YuMi/Modules/YMNewHome/View/XPNewHomeViewController.m +++ b/YuMi/Modules/YMNewHome/View/XPNewHomeViewController.m @@ -13,7 +13,6 @@ #import #import #import "SDWebImageManager.h" -#import "FirstRechargeManager.h" ///Tool #import "Api+Home.h" #import "YUMIMacroUitls.h" @@ -330,8 +329,7 @@ JXCategoryViewDelegate, XPHomeContainerProtocol, XPNewHomeNavViewDelegate, XPNewHomeHeadViewDelegate, -XPHomeRecommendOtherRoomViewDelegate, -FirstRechargeManagerDelegate> +XPHomeRecommendOtherRoomViewDelegate> ///头视图 @property(nonatomic,strong) XPNewHomeHeadView *headView; @@ -359,11 +357,6 @@ FirstRechargeManagerDelegate> @property (nonatomic, assign) bool hasLoadAPIs; -// 首充弹窗引用 -@property (nonatomic, strong) XPWebViewController *firstChargeWebVC; -// 首充弹窗背景视图 -@property (nonatomic, strong) UIView *firstChargeBackgroundView; - @end @implementation XPNewHomeViewController @@ -377,7 +370,6 @@ FirstRechargeManagerDelegate> -(void)dealloc{ [[NSNotificationCenter defaultCenter]removeObserver:self]; - [FirstRechargeManager sharedManager].delegate = nil; } - (BOOL)isHiddenNavBar { @@ -398,12 +390,6 @@ FirstRechargeManagerDelegate> [self initSubViewConstraints]; [self requestCheckIp]; - - // 注册首充管理器监听 - [FirstRechargeManager sharedManager].delegate = self; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [[FirstRechargeManager sharedManager] startMonitoring]; - }); } } @@ -416,13 +402,10 @@ FirstRechargeManagerDelegate> }); self.hasLoadAPIs = YES; } - + } - (void)tokenInvalid { - // 停止首充监控 - [[FirstRechargeManager sharedManager] stopMonitoring]; - [[AccountInfoStorage instance] saveAccountInfo:nil]; [[AccountInfoStorage instance] saveTicket:nil]; if ([NIMSDK sharedSDK].loginManager.isLogined) { @@ -786,135 +769,7 @@ FirstRechargeManagerDelegate> self.type = index; } -- (void)getFirstChargeSuccess:(FirstRechargeModel *)model { - if (model.chargeStatus == NO) { - // TODO: 弹出窗口 web view - XPWebViewController *web = [[XPWebViewController alloc] initWithRoomUID:@""]; - [self addChildViewController:web]; - [self.view addSubview:web.view]; - web.view.frame = CGRectMake(0, 0, KScreenWidth/2, KScreenHeight/2); - web.view.center = self.view.center; - } -} -#pragma mark - FirstRechargeManagerDelegate - -- (void)firstRechargeManager:(FirstRechargeManager *)manager didCheckFirstRecharge:(FirstRechargeModel *)model shouldShow:(BOOL)shouldShow { - if (shouldShow && model.chargeStatus == NO) { - // 展示首充弹窗 - [self showFirstRechargePopup:model]; - - // 标记今天已展示过(在展示后标记) - [[FirstRechargeManager sharedManager] markTodayShown]; - } -} - -- (void)showFirstRechargePopup:(FirstRechargeModel *)model { - if (model.chargeStatus) { - return; - } - - // 如果已经有弹窗在显示,先移除它 - [self removeFirstChargePopup]; - - // 获取 keyWindow 以确保盖在 tabbar 上 - UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; - if (!keyWindow) { - keyWindow = [UIApplication sharedApplication].windows.firstObject; - } - - // 创建背景视图,使用 keyWindow 的 bounds - self.firstChargeBackgroundView = [[UIView alloc] initWithFrame:keyWindow.bounds]; - self.firstChargeBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; - - // 添加点击手势 - UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundTap:)]; - [self.firstChargeBackgroundView addGestureRecognizer:tapGesture]; - - // 添加到 keyWindow 上,确保盖在 tabbar 上 - [keyWindow addSubview:self.firstChargeBackgroundView]; - - // 创建并配置 web 视图 - self.firstChargeWebVC = [[XPWebViewController alloc] initWithRoomUID:@""]; - self.firstChargeWebVC.isPush = NO; - self.firstChargeWebVC.url = URLWithType(kFirstChargeHomeIndex); - - // 获取当前的根视图控制器来添加子控制器 - UIViewController *rootViewController = keyWindow.rootViewController; - if ([rootViewController isKindOfClass:[UINavigationController class]]) { - rootViewController = [(UINavigationController *)rootViewController topViewController]; - } else if ([rootViewController isKindOfClass:[UITabBarController class]]) { - UITabBarController *tabBarController = (UITabBarController *)rootViewController; - rootViewController = tabBarController.selectedViewController; - if ([rootViewController isKindOfClass:[UINavigationController class]]) { - rootViewController = [(UINavigationController *)rootViewController topViewController]; - } - } - - [rootViewController addChildViewController:self.firstChargeWebVC]; - [self.firstChargeBackgroundView addSubview:self.firstChargeWebVC.view]; - [self.firstChargeWebVC didMoveToParentViewController:rootViewController]; - - self.firstChargeWebVC.view.backgroundColor = [UIColor clearColor]; - self.firstChargeWebVC.webview.backgroundColor = [UIColor clearColor]; - self.firstChargeWebVC.webview.opaque = NO; - self.firstChargeWebVC.webview.scrollView.backgroundColor = [UIColor clearColor]; - self.firstChargeWebVC.view.frame = CGRectMake(0, 0, KScreenWidth - 40, KScreenHeight*2/3); - self.firstChargeWebVC.view.center = self.firstChargeBackgroundView.center; - - // 添加关闭按钮 -// UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; -// [closeButton setTitle:@"✕" forState:UIControlStateNormal]; -// [closeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; -// closeButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightBold]; -// [closeButton addTarget:self action:@selector(removeFirstChargePopup) forControlEvents:UIControlEventTouchUpInside]; -// closeButton.frame = CGRectMake(0, 0, 30, 30); -// closeButton.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; -// closeButton.layer.cornerRadius = 15; -// [self.firstChargeBackgroundView addSubview:closeButton]; -// -// // 设置关闭按钮位置(webView 右上角) -// CGRect webViewFrame = self.firstChargeWebVC.view.frame; -// closeButton.center = CGPointMake(CGRectGetMaxX(webViewFrame) - 15, CGRectGetMinY(webViewFrame) + 15); - - // 3秒后自动移除 -// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ -// [self removeFirstChargePopup]; -// }); -} - -// 处理背景点击事件 -- (void)handleBackgroundTap:(UITapGestureRecognizer *)gesture { - // 获取点击位置 - CGPoint location = [gesture locationInView:self.firstChargeBackgroundView]; - - // 检查是否点击在 webView 区域外 - if (self.firstChargeWebVC && self.firstChargeWebVC.view) { - CGRect webViewFrame = self.firstChargeWebVC.view.frame; - if (!CGRectContainsPoint(webViewFrame, location)) { - // 点击在 webView 外部,移除弹窗 - [self removeFirstChargePopup]; - } - } else { - // 如果 webView 不存在,直接移除弹窗 - [self removeFirstChargePopup]; - } -} - -// 修改移除弹窗的方法 -- (void)removeFirstChargePopup { - if (self.firstChargeWebVC) { - [self.firstChargeWebVC willMoveToParentViewController:nil]; - [self.firstChargeWebVC.view removeFromSuperview]; - [self.firstChargeWebVC removeFromParentViewController]; - self.firstChargeWebVC = nil; - } - - if (self.firstChargeBackgroundView) { - [self.firstChargeBackgroundView removeFromSuperview]; - self.firstChargeBackgroundView = nil; - } -} #pragma mark - XPNewHomeHeadViewDelegate /// diff --git a/YuMi/Modules/YMRoom/Presenter/XPRoomPresenter.m b/YuMi/Modules/YMRoom/Presenter/XPRoomPresenter.m index 7cedef76..718639f5 100644 --- a/YuMi/Modules/YMRoom/Presenter/XPRoomPresenter.m +++ b/YuMi/Modules/YMRoom/Presenter/XPRoomPresenter.m @@ -106,9 +106,7 @@ [[NIMSDK sharedSDK].chatroomManager enterChatroom:request completion:^(NSError * _Nullable error, NIMChatroom * _Nullable chatroom, NIMChatroomMember * _Nullable me) { @kStrongify(self); if (error) { -#ifdef DEBUG - NSLog(@"\n云信进房异常:\n %@", error); -#endif + NSLog(@"\n云信进房异常:\n %@", error); [[self getView] enterRoomFail:error.code]; } else { [[self getView] enterRoomSuccess:chatroom]; diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageInfoModel.m b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageInfoModel.m index 0469cee4..f27713d9 100644 --- a/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageInfoModel.m +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageInfoModel.m @@ -36,6 +36,11 @@ CGSize size = [content boundingRectWithSize:CGSizeMake(width, 0) options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin context:nil].size; + + if (self.first == 9 && self.second == 91) { + self.rowHeight = 100; + return; + } YYTextContainer *container = [YYTextContainer containerWithSize:CGSizeMake(size.width, CGFLOAT_MAX)]; container.maximumNumberOfRows = 0; @@ -45,6 +50,7 @@ CGSize textSize = textLayout.textBoundingRect.size; self.textWidth = textSize.width; + if ([NSString isEmpty:self.bubbleImageUrl]) { self.rowHeight = ceil(textSize.height) + @@ -61,5 +67,19 @@ self.rowHeight += 10; // } self.rowHeight += (self.isBoom ? 20 : 0); + if ([self isStringContainArabic:content.string]) { + self.rowHeight += 20; + } } + +- (BOOL)isStringContainArabic:(NSString *)string { + for (NSUInteger i = 0; i < string.length; i++) { + unichar c = [string characterAtIndex:i]; + if (c >= 0x0600 && c <= 0x06FF) { + return YES; + } + } + return NO; +} + @end diff --git a/YuMi/Modules/YMRoom/View/MessageContainerView/Tool/XPRoomMessageParser.m b/YuMi/Modules/YMRoom/View/MessageContainerView/Tool/XPRoomMessageParser.m index d5323404..250744b4 100644 --- a/YuMi/Modules/YMRoom/View/MessageContainerView/Tool/XPRoomMessageParser.m +++ b/YuMi/Modules/YMRoom/View/MessageContainerView/Tool/XPRoomMessageParser.m @@ -1012,6 +1012,9 @@ - (XPMessageInfoModel*)createRoomFaceAttribute:(AttachmentModel *)attachment messageInfo:(XPMessageInfoModel *)messageInfo remoteExtModel:(XPMessageRemoteExtModel *)model { + messageInfo.first = attachment.first; + messageInfo.second = attachment.second; + NSMutableAttributedString * attribute = [[NSMutableAttributedString alloc] init]; ///官方新用户 [attribute appendAttributedString:[self createOfficalAndNewuserAttribute:model.defUser newUser:model.newUser fromSayHelloChannel:model.fromSayHelloChannel]]; @@ -1094,7 +1097,6 @@ } } messageInfo.content = attribute; - messageInfo.first = attachment.first; return messageInfo; } diff --git a/YuMi/Modules/YMRoom/View/SendGiftView/View/XPGiftBarView.m b/YuMi/Modules/YMRoom/View/SendGiftView/View/XPGiftBarView.m index d4631d63..340abc15 100644 --- a/YuMi/Modules/YMRoom/View/SendGiftView/View/XPGiftBarView.m +++ b/YuMi/Modules/YMRoom/View/SendGiftView/View/XPGiftBarView.m @@ -141,7 +141,7 @@ FirstRechargeModel *model = [[FirstRechargeManager sharedManager] loadCurrentModel]; if (model && model.chargeStatus == NO) { self.firstChargeView.hidden = NO; - [self.moneyLabel updateContent:@"Bonus >"]; + [self.moneyLabel updateContent:YMLocalizedString(@"20.20.61_text_19")]; } else { self.firstChargeView.hidden = YES; } @@ -164,9 +164,18 @@ }]; [self.firstChargeView mas_makeConstraints:^(MASConstraintMaker *make) { - make.size.mas_equalTo(CGSizeMake(68, 23)); + make.height.mas_equalTo(23); make.leading.mas_equalTo(self.rechargeStackView.mas_leading); make.centerY.mas_equalTo(self.rechargeStackView); + if (isMSZH()) { + make.width.mas_equalTo(80); + } else if (isMSRTL()) { + make.width.mas_equalTo(76); + } else if (isMSTR()) { + make.width.mas_equalTo(70); + } else { + make.width.mas_equalTo(70); + } }]; [self.sendOperationView mas_makeConstraints:^(MASConstraintMaker *make) { @@ -546,7 +555,19 @@ [_firstChargeView addSubview:self.moneyLabel]; [self.moneyLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.mas_equalTo(_firstChargeView).insets(UIEdgeInsetsMake(5, 5, 5, 5)); + make.edges.mas_equalTo(_firstChargeView).insets(UIEdgeInsetsMake(5, 5, 5, 10)); + }]; + + UIImageView *arrow = [[UIImageView alloc] initWithImage:[kImage(@"brown_arrow") ms_SetImageForRTL]]; + [_firstChargeView addSubview:arrow]; + [arrow mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(_firstChargeView); + if (isMSRTL()) { + make.leading.mas_equalTo(self.moneyLabel.mas_trailing).offset(-10); + }else { + make.leading.mas_equalTo(self.moneyLabel.mas_trailing).offset(-5); + } + make.size.mas_equalTo(CGSizeMake(12, 12)); }]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTagRecharge:)]; @@ -557,7 +578,7 @@ - (MoliMoneyLabel *)moneyLabel { if (!_moneyLabel) { - _moneyLabel = [MoliMoneyLabel moneyLabelWithTextColot:UIColorFromRGB(0x582B00) font:kFontSemibold(10) moneyPostion:1 moneySize:CGSizeMake(15, 15)]; + _moneyLabel = [MoliMoneyLabel moneyLabelWithTextColot:UIColorFromRGB(0x582B00) font:kFontSemibold(12) moneyPostion:1 moneySize:CGSizeMake(15, 15)]; } return _moneyLabel; } diff --git a/YuMi/Modules/YMRoom/View/UserCard/View/UserRoomCardViewController.m b/YuMi/Modules/YMRoom/View/UserCard/View/UserRoomCardViewController.m index 7b95d747..d233ee83 100644 --- a/YuMi/Modules/YMRoom/View/UserCard/View/UserRoomCardViewController.m +++ b/YuMi/Modules/YMRoom/View/UserCard/View/UserRoomCardViewController.m @@ -461,7 +461,7 @@ [self.medalsScrollView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self.contentView); - make.centerY.mas_equalTo(self.contentView); + make.top.mas_equalTo(self.contentView); make.height.mas_equalTo(kGetScaleWidth(30)); // 初始设置一个默认宽度,后续会动态更新 self.scrollViewWidthConstraint = make.width.mas_equalTo(maxMedalsWidth); diff --git a/YuMi/Modules/YMWeb/XPWebViewController.h b/YuMi/Modules/YMWeb/XPWebViewController.h index 2f3d2122..eda89836 100644 --- a/YuMi/Modules/YMWeb/XPWebViewController.h +++ b/YuMi/Modules/YMWeb/XPWebViewController.h @@ -43,6 +43,8 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy) void (^verifyCaptcha)(BOOL result); +@property (nonatomic, copy) void(^didTapCharge)(void); + - (instancetype)initWithCustomizeNav:(BOOL)isCustom; ///强制使用 roomUID 初始化,尽量传入,防止 web 在需要时取不到数据 diff --git a/YuMi/Modules/YMWeb/XPWebViewController.m b/YuMi/Modules/YMWeb/XPWebViewController.m index 99a155a5..a708a43d 100644 --- a/YuMi/Modules/YMWeb/XPWebViewController.m +++ b/YuMi/Modules/YMWeb/XPWebViewController.m @@ -373,9 +373,10 @@ NSString * const kJSShowShareCallBack = @"showShareAction"; NSString *currentUrl = [NSString stringWithFormat:@"%@", response]; ///测试环境只要有host就执行,方便h5连接本地调试 BOOL condition = currentUrl != nil && [currentUrl containsString:API_HOST_URL]; -#ifdef DEBUG + NSLog(@"-- -- - -- - - | -- -- - -- - -%@", response); NSLog(@"-- -- - -- - -%@: %@", message.name, message.body); +#ifdef DEBUG condition = currentUrl != nil; #endif if(condition) { @@ -450,6 +451,11 @@ NSString * const kJSShowShareCallBack = @"showShareAction"; webVC.type = @"4"; [[XCCurrentVCStackManager shareManager].getCurrentVC.navigationController pushViewController:webVC animated:YES]; } + + if (self.didTapCharge) { + self.didTapCharge(); + } + } else if ([message.name isEqualToString:kOpenPersonPage]) { NSString *uid = [NSString stringWithFormat:@"%@",message.body]; if (uid.integerValue > 0) { diff --git a/YuMi/Structure/MVP/Model/MedalModel.h b/YuMi/Structure/MVP/Model/MedalModel.h index e0b089da..09868a0f 100644 --- a/YuMi/Structure/MVP/Model/MedalModel.h +++ b/YuMi/Structure/MVP/Model/MedalModel.h @@ -71,6 +71,8 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) NSInteger busType; +@property (nonatomic,copy) NSString *mp4Url; + @end @interface MedalModel : PIBaseModel diff --git a/YuMi/Structure/MVP/View/MvpViewController.m b/YuMi/Structure/MVP/View/MvpViewController.m index 1d1b1621..b9ff4eda 100644 --- a/YuMi/Structure/MVP/View/MvpViewController.m +++ b/YuMi/Structure/MVP/View/MvpViewController.m @@ -125,12 +125,18 @@ - (void)accountCanceled:(NSDictionary *)data { NSString *date = [NSString stringWithFormat:@"%.0f",[[data objectForKey:@"cancelDate"] doubleValue]]; NSString *dateDes = [NSString stringWithFormat:YMLocalizedString(@"MvpViewController0"), [PLTimeUtil getDateWithYYMMDD:date]]; - NSString *msg = [NSString stringWithFormat:YMLocalizedString(@"MvpViewController1"), dateDes]; + NSString *msg = [NSString stringWithFormat:YMLocalizedString(@"MvpViewController1.1"), dateDes]; + if ([[data objectForKey:@"cancelDate"] doubleValue] == 0) { + dateDes = @""; + msg = YMLocalizedString(@"MvpViewController1.2"); + } + TTAlertMessageAttributedConfig *dateAttrConfig = [[TTAlertMessageAttributedConfig alloc] init]; dateAttrConfig.text = dateDes; dateAttrConfig.color = DJDKMIMOMColor.appMainColor; TTAlertConfig *config = [[TTAlertConfig alloc] init]; + config.actionStyle = 0; config.title = YMLocalizedString(@"MvpViewController2"); config.message = msg; config.messageAttributedConfig = @[dateAttrConfig]; diff --git a/YuMi/Tools/FirstCharge/FirstRechargeManager.m b/YuMi/Tools/FirstCharge/FirstRechargeManager.m index f45e83db..b579a7ef 100644 --- a/YuMi/Tools/FirstCharge/FirstRechargeManager.m +++ b/YuMi/Tools/FirstCharge/FirstRechargeManager.m @@ -15,14 +15,14 @@ static NSString * const kFirstRechargeModelKey = @"FirstRechargeModel"; static NSString * const kLastCheckDateKey = @"FirstRechargeLastCheckDate"; static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; - - +static NSString * const kCurrentUserIdKey = @"FirstRechargeCurrentUserId"; @interface FirstRechargeManager () @property (nonatomic, strong) FirstRechargeModel *currentFirstRechargeData; @property (nonatomic, strong) NSTimer *dailyTimer; @property (nonatomic, assign) BOOL isMonitoring; +@property (nonatomic, copy) NSString *currentUserId; @end @@ -42,6 +42,7 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; if (self) { _isMonitoring = NO; [self loadCachedModel]; + [self updateCurrentUserId]; } return self; } @@ -63,6 +64,9 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; self.isMonitoring = YES; + // 检查用户是否切换 + [self checkUserSwitch]; + // 立即检查一次 [self checkFirstRechargeStatus]; @@ -99,8 +103,7 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; - (void)markTodayShown { NSString *today = [self getTodayString]; - [[NSUserDefaults standardUserDefaults] setObject:today forKey:kTodayShownKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [self setTodayShownForCurrentUser:today]; } #pragma mark - Private Methods @@ -140,6 +143,9 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; } - (void)applicationDidBecomeActive { + // 检查用户是否切换 + [self checkUserSwitch]; + // 应用进入前台时检查是否需要更新 if ([self shouldCheckToday]) { [self checkFirstRechargeStatus]; @@ -147,7 +153,8 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; } - (BOOL)shouldCheckToday { - NSString *lastCheckDate = [[NSUserDefaults standardUserDefaults] objectForKey:kLastCheckDateKey]; + NSString *userSpecificKey = [self getUserSpecificKey:kLastCheckDateKey]; + NSString *lastCheckDate = [[NSUserDefaults standardUserDefaults] objectForKey:userSpecificKey]; NSString *today = [self getTodayString]; return ![today isEqualToString:lastCheckDate]; @@ -160,6 +167,9 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; return; } + // 更新当前用户ID + [self updateCurrentUserId]; + // 调用API获取首充信息 @kWeakify(self); [Api firstchargeInfo:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { @@ -180,9 +190,10 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; self.currentFirstRechargeData = model; [self saveCachedModel:model]; - // 更新检查日期 + // 更新检查日期(针对当前用户) NSString *today = [self getTodayString]; - [[NSUserDefaults standardUserDefaults] setObject:today forKey:kLastCheckDateKey]; + NSString *userSpecificKey = [self getUserSpecificKey:kLastCheckDateKey]; + [[NSUserDefaults standardUserDefaults] setObject:today forKey:userSpecificKey]; [[NSUserDefaults standardUserDefaults] synchronize]; // 判断是否需要展示 @@ -193,16 +204,13 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; } - (BOOL)shouldShowFirstRecharge:(FirstRechargeModel *)model { -#if DEBUG - return YES; -#endif // 如果已经首充过,不展示 if (model.chargeStatus) { return NO; } - // 检查今天是否已经展示过 - NSString *shownDate = [[NSUserDefaults standardUserDefaults] objectForKey:kTodayShownKey]; + // 检查今天是否已经展示过(针对当前用户) + NSString *shownDate = [self getTodayShownForCurrentUser]; NSString *today = [self getTodayString]; return ![today isEqualToString:shownDate]; @@ -224,16 +232,73 @@ static NSString * const kTodayShownKey = @"FirstRechargeTodayShown"; return [formatter stringFromDate:[NSDate date]]; } +#pragma mark - User Switch Detection + +- (void)updateCurrentUserId { + NSString *newUserId = [AccountInfoStorage instance].getUid; + if (newUserId.length > 0) { + self.currentUserId = newUserId; + // 保存当前用户ID + [[NSUserDefaults standardUserDefaults] setObject:newUserId forKey:kCurrentUserIdKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} + +- (void)checkUserSwitch { + NSString *savedUserId = [[NSUserDefaults standardUserDefaults] objectForKey:kCurrentUserIdKey]; + NSString *currentUserId = [AccountInfoStorage instance].getUid; + + // 如果用户ID发生变化,说明用户切换了 + if (currentUserId.length > 0 && + savedUserId.length > 0 && + ![currentUserId isEqualToString:savedUserId]) { + + // 更新当前用户ID + [self updateCurrentUserId]; + + // 清除当前缓存的模型数据(因为是新用户) + self.currentFirstRechargeData = nil; + + // 立即检查新用户的首充状态 + [self checkFirstRechargeStatus]; + } else if (currentUserId.length > 0 && savedUserId.length == 0) { + // 首次登录的情况 + [self updateCurrentUserId]; + } +} + +#pragma mark - User-specific Storage + +- (NSString *)getUserSpecificKey:(NSString *)baseKey { + if (self.currentUserId.length == 0) { + return baseKey; + } + return [NSString stringWithFormat:@"%@_%@", baseKey, self.currentUserId]; +} + +- (NSString *)getTodayShownForCurrentUser { + NSString *userSpecificKey = [self getUserSpecificKey:kTodayShownKey]; + return [[NSUserDefaults standardUserDefaults] objectForKey:userSpecificKey]; +} + +- (void)setTodayShownForCurrentUser:(NSString *)date { + NSString *userSpecificKey = [self getUserSpecificKey:kTodayShownKey]; + [[NSUserDefaults standardUserDefaults] setObject:date forKey:userSpecificKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + - (void)saveCachedModel:(FirstRechargeModel *)model { if (model) { + NSString *userSpecificKey = [self getUserSpecificKey:kFirstRechargeModelKey]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:model.toJSONObject requiringSecureCoding:NO error:nil]; - [[NSUserDefaults standardUserDefaults] setObject:data forKey:kFirstRechargeModelKey]; + [[NSUserDefaults standardUserDefaults] setObject:data forKey:userSpecificKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } } - (void)loadCachedModel { - NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:kFirstRechargeModelKey]; + NSString *userSpecificKey = [self getUserSpecificKey:kFirstRechargeModelKey]; + NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:userSpecificKey]; if (data) { NSDictionary *dict = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSDictionary class] fromData:data error:nil]; if (dict) { diff --git a/YuMi/Tools/MedalMediaDisplayManager.h b/YuMi/Tools/MedalMediaDisplayManager.h new file mode 100644 index 00000000..315c3cab --- /dev/null +++ b/YuMi/Tools/MedalMediaDisplayManager.h @@ -0,0 +1,101 @@ +// +// MedalMediaDisplayManager.h +// YuMi +// +// Created by AI on 2025/1/20. +// + +#import +#import + +@class VAPView, NetImageView, XPRoomGiftAnimationParser; + +NS_ASSUME_NONNULL_BEGIN + +/** + * 媒体显示代理协议 + * 需要使用媒体显示功能的类实现此协议 + */ +@protocol MedalMediaDisplayDelegate + +@required +/// 从数据模型中获取MP4 URL +- (NSString * _Nullable)getMP4UrlFromModel:(id _Nullable)model; + +/// 从数据模型中获取图片 URL +- (NSString * _Nullable)getPicUrlFromModel:(id _Nullable)model; + +/// 获取图片显示视图 +- (NetImageView *)getImageView; + +/// 获取MP4播放视图 +- (VAPView *)getMP4View; + +@optional +/// 媒体显示状态更新回调 +- (void)onMediaDisplayUpdated:(BOOL)isMP4 success:(BOOL)success; + +/// 获取默认占位图 +- (UIImage * _Nullable)getDefaultPlaceholderImage; + +@end + +/** + * 勋章媒体显示管理器 + * 统一处理MP4和PNG的显示逻辑 + */ +@interface MedalMediaDisplayManager : NSObject + +/// 代理对象 +@property (nonatomic, weak) id delegate; + +/// 当前显示的数据模型 +@property (nonatomic, strong, nullable) id currentModel; + +/// 可见性状态 +@property (nonatomic, assign) BOOL isVisible; + +/// 当前媒体路径 +@property (nonatomic, copy, readonly, nullable) NSString *currentImagePath; +@property (nonatomic, copy, readonly, nullable) NSString *currentMP4Path; + +/** + * 初始化方法 + * @param delegate 实现MedalMediaDisplayDelegate协议的对象 + */ +- (instancetype)initWithDelegate:(id)delegate; + +/** + * 更新显示内容 + * @param model 数据模型 + */ +- (void)updateDisplayWithModel:(id _Nullable)model; + +/** + * 可见性管理 + */ +- (void)willDisplay; +- (void)didEndDisplaying; + +/** + * 播放控制 + */ +- (void)stopMP4Playback; +- (void)pauseMP4Playback; +- (void)resumeMP4Playback; + +/** + * 资源清理 + */ +- (void)cleanupResources; + +/** + * 应用生命周期通知处理 + */ +- (void)handleAppDidEnterBackground; +- (void)handleAppWillEnterForeground; +- (void)handleMemoryWarning; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/YuMi/Tools/MedalMediaDisplayManager.m b/YuMi/Tools/MedalMediaDisplayManager.m new file mode 100644 index 00000000..86dffd16 --- /dev/null +++ b/YuMi/Tools/MedalMediaDisplayManager.m @@ -0,0 +1,328 @@ +// +// MedalMediaDisplayManager.m +// YuMi +// +// Created by AI on 2025/1/20. +// + +#import "MedalMediaDisplayManager.h" +#import +#import "XPRoomGiftAnimationParser.h" +#import "NSString+XPExtension.h" +#import "UIImageConstant.h" + +@interface MedalMediaDisplayManager () + +@property (nonatomic, copy, readwrite, nullable) NSString *currentImagePath; +@property (nonatomic, copy, readwrite, nullable) NSString *currentMP4Path; +@property (nonatomic, strong) XPRoomGiftAnimationParser *mp4Parser; +@property (nonatomic, assign) NSInteger mp4PlaybackTag; // 播放状态标记 + +@end + +@implementation MedalMediaDisplayManager + +#pragma mark - 初始化 + +- (instancetype)initWithDelegate:(id)delegate { + self = [super init]; + if (self) { + _delegate = delegate; + _isVisible = YES; + _mp4PlaybackTag = 0; + [self setupNotifications]; + } + return self; +} + +- (void)dealloc { + [self cleanupResources]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + NSLog(@"MedalMediaDisplayManager dealloc"); +} + +#pragma mark - 公共接口 + +- (void)updateDisplayWithModel:(id)model { + self.currentModel = model; + [self handleMediaDisplayWithModel:model]; +} + +- (void)willDisplay { + self.isVisible = YES; + [self resumeMP4PlaybackIfNeeded]; +} + +- (void)didEndDisplaying { + self.isVisible = NO; + [self pauseMP4Playback]; +} + +- (void)stopMP4Playback { + VAPView *mp4View = [self.delegate getMP4View]; + if (mp4View) { + [mp4View stopHWDMP4]; + self.mp4PlaybackTag = 0; + } +} + +- (void)pauseMP4Playback { + VAPView *mp4View = [self.delegate getMP4View]; + if (mp4View && !mp4View.hidden) { + [mp4View pauseHWDMP4]; + } +} + +- (void)resumeMP4Playback { + [self resumeMP4PlaybackIfNeeded]; +} + +- (void)cleanupResources { + [self stopMP4Playback]; + self.mp4Parser = nil; + self.currentImagePath = nil; + self.currentMP4Path = nil; + self.currentModel = nil; +} + +#pragma mark - 核心媒体处理逻辑 + +- (void)handleMediaDisplayWithModel:(id)model { + if (!model || !self.delegate) { + [self showDefaultPlaceholder]; + return; + } + + NSString *mp4Url = [self.delegate getMP4UrlFromModel:model]; + NSString *picUrl = [self.delegate getPicUrlFromModel:model]; + + // 优先判断 MP4 URL + if (![NSString isEmpty:mp4Url] && [self isValidMP4URL:mp4Url]) { + [self setMp4Path:mp4Url]; + return; + } + + // MP4 URL 无效,检查图片 URL + if (![NSString isEmpty:picUrl]) { + if ([self isValidMP4URL:picUrl]) { + // picUrl 实际上是 MP4(兼容旧逻辑) + [self setMp4Path:picUrl]; + return; + } else if ([NSString isValidImageURL:picUrl]) { + // 有效的图片 URL + [self setImagePath:picUrl]; + return; + } + } + + // 都无效,显示默认占位图 + [self showDefaultPlaceholder]; +} + +- (void)setImagePath:(NSString *)imagePath { + [self stopMP4Playback]; + + self.currentImagePath = imagePath; + self.currentMP4Path = nil; + + VAPView *mp4View = [self.delegate getMP4View]; + NetImageView *imageView = [self.delegate getImageView]; + + mp4View.hidden = YES; + imageView.hidden = NO; + imageView.imageUrl = imagePath ?: @""; + + [self notifyDisplayUpdated:NO success:![NSString isEmpty:imagePath]]; +} + +- (void)setMp4Path:(NSString *)mp4Path { + if ([NSString isEmpty:mp4Path]) { + [self showDefaultPlaceholder]; + return; + } + + [self stopMP4Playback]; + + self.currentMP4Path = mp4Path; + self.currentImagePath = nil; + + VAPView *mp4View = [self.delegate getMP4View]; + NetImageView *imageView = [self.delegate getImageView]; + + mp4View.hidden = NO; + imageView.hidden = YES; + + if (!self.mp4Parser) { + self.mp4Parser = [[XPRoomGiftAnimationParser alloc] init]; + } + + @kWeakify(self); + [self.mp4Parser parseWithURL:mp4Path + completionBlock:^(NSString * _Nullable videoUrl) { + @kStrongify(self); + if (![NSString isEmpty:videoUrl]) { + self.mp4PlaybackTag = 1; // 标记已准备好播放 + + if (self.isVisible) { + [self startMP4PlaybackWithURL:videoUrl]; + } + } + } failureBlock:^(NSError * _Nullable error) { + @kStrongify(self); + NSLog(@"[MedalMediaDisplayManager] Failed to parse mp4: %@", error); + [self handleMP4FailureWithFallback]; + }]; +} + +- (void)startMP4PlaybackWithURL:(NSString *)videoUrl { + if ([NSString isEmpty:videoUrl] || !self.delegate) { + return; + } + + VAPView *mp4View = [self.delegate getMP4View]; + if (mp4View && !mp4View.hidden) { + [mp4View playHWDMP4:videoUrl repeatCount:-1 delegate:self]; + [self notifyDisplayUpdated:YES success:YES]; + NSLog(@"[MedalMediaDisplayManager] Started MP4 playback: %@", videoUrl); + } +} + +- (void)handleMP4FailureWithFallback { + if (!self.delegate || !self.currentModel) { + [self showDefaultPlaceholder]; + return; + } + + // MP4 失败,尝试降级到图片 + NSString *picUrl = [self.delegate getPicUrlFromModel:self.currentModel]; + if (![NSString isEmpty:picUrl] && [NSString isValidImageURL:picUrl]) { + [self setImagePath:picUrl]; + } else { + [self showDefaultPlaceholder]; + } +} + +- (void)showDefaultPlaceholder { + [self stopMP4Playback]; + + VAPView *mp4View = [self.delegate getMP4View]; + NetImageView *imageView = [self.delegate getImageView]; + + mp4View.hidden = YES; + imageView.hidden = NO; + imageView.imageUrl = @""; + + // 获取默认占位图 + UIImage *placeholderImage = nil; + if ([self.delegate respondsToSelector:@selector(getDefaultPlaceholderImage)]) { + placeholderImage = [self.delegate getDefaultPlaceholderImage]; + } + if (!placeholderImage) { + placeholderImage = [UIImageConstant defaultEmptyPlaceholder]; + } + imageView.image = placeholderImage; + + [self notifyDisplayUpdated:NO success:NO]; +} + +- (void)resumeMP4PlaybackIfNeeded { + if (!self.isVisible || [NSString isEmpty:self.currentMP4Path]) { + return; + } + + VAPView *mp4View = [self.delegate getMP4View]; + if (mp4View && !mp4View.hidden) { + if (self.mp4PlaybackTag == 1) { + // 已准备好但尚未播放,重新解析并播放 + @kWeakify(self); + [self.mp4Parser parseWithURL:self.currentMP4Path + completionBlock:^(NSString * _Nullable videoUrl) { + @kStrongify(self); + if (![NSString isEmpty:videoUrl] && self.isVisible) { + [self startMP4PlaybackWithURL:videoUrl]; + } + } failureBlock:^(NSError * _Nullable error) { + @kStrongify(self); + [self handleMP4FailureWithFallback]; + }]; + } else { + // 恢复暂停的播放 + [mp4View resumeHWDMP4]; + } + } +} + +#pragma mark - 辅助方法 + +- (BOOL)isValidMP4URL:(NSString *)url { + if ([NSString isEmpty:url]) { + return NO; + } + + NSString *lowercaseUrl = [url lowercaseString]; + return [lowercaseUrl hasSuffix:@".mp4"] || + [lowercaseUrl containsString:@"mp4"] || + [lowercaseUrl containsString:@"video"]; +} + +- (void)notifyDisplayUpdated:(BOOL)isMP4 success:(BOOL)success { + if ([self.delegate respondsToSelector:@selector(onMediaDisplayUpdated:success:)]) { + [self.delegate onMediaDisplayUpdated:isMP4 success:success]; + } +} + +#pragma mark - 通知处理 + +- (void)setupNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleAppDidEnterBackground) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleAppWillEnterForeground) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleMemoryWarning) + name:UIApplicationDidReceiveMemoryWarningNotification + object:nil]; +} + +- (void)handleAppDidEnterBackground { + [self pauseMP4Playback]; +} + +- (void)handleAppWillEnterForeground { + if (self.isVisible) { + [self resumeMP4PlaybackIfNeeded]; + } +} + +- (void)handleMemoryWarning { + if (!self.isVisible) { + [self stopMP4Playback]; + } +} + +#pragma mark - HWDMP4PlayDelegate + +- (BOOL)shouldStartPlayMP4:(VAPView *)container config:(QGVAPConfigModel *)config { + return YES; +} + +- (void)viewDidFinishPlayMP4:(NSInteger)totalFrameCount view:(VAPView *)container { + // MP4 播放完成,循环播放会自动重新开始 +} + +- (void)viewDidStopPlayMP4:(NSInteger)lastFrameIndex view:(VAPView *)container { + // MP4 播放停止 +} + +- (void)viewDidFailPlayMP4:(NSError *)error { + NSLog(@"[MedalMediaDisplayManager] MP4 播放失败: %@", error); + [self handleMP4FailureWithFallback]; +} + +@end \ No newline at end of file diff --git a/YuMi/ar.lproj/Localizable.strings b/YuMi/ar.lproj/Localizable.strings index 0fdf0a19..19035488 100644 --- a/YuMi/ar.lproj/Localizable.strings +++ b/YuMi/ar.lproj/Localizable.strings @@ -2941,8 +2941,8 @@ ineHeadView12" = "الحمل"; "LoginBindPhoneViewController2" = "ربط رقم الهاتف المحمول"; "MvpViewController0" = "وقت الإلغاء: %@"; -"MvpViewController1" = "%@\n\nيرجى الاتصال بخدمة العملاء (WeChat: mxyz2050) للحصول على استفسارات"; -"MvpViewController2" = "تم إلغاء تسجيل الحساب"; +"MvpViewController1.1" = "@%\n\nيرجى الاتصال بخدمة العملاء (WeChat: mxyz2050) للحصول على استفسارات"; +"MvpViewController1.2" = "يرجى الاتصال بخدمة العملاء (WeChat: mxyz2050) للحصول على استفسارات"; "MvpViewController3" = "من أجل خلق بيئة إنترنت آمنة أكثر\nحماية ممتلكاتك وممتلكات الآخرين\nيرجى تقديم التحقق الهوية أولا"; "MvpViewController5" = "التحقق من الهوية"; @@ -4206,3 +4206,24 @@ ineHeadView12" = "الحمل"; "20.20.59_text_34" = "تم التقديم بنجاح"; "20.20.59_text_35" = "تم الاشتراك بنجاح"; "20.20.59_text_36" = "تم الإلغاء بنجاح"; + +"20.20.61_text_1" = "ميدالياتي"; +"20.20.61_text_2" = "حامل الميدالية"; +"20.20.61_text_3" = "ميداليات المهام"; +"20.20.61_text_4" = "ميداليات الأنشطة"; +"20.20.61_text_5" = "ميداليات المجد"; +"20.20.61_text_6" = "لا ترتدي أي ميداليات"; +"20.20.61_text_7.1" = "لم تستلم أي ميداليات"; +"20.20.61_text_7.2" = "لم تستلم أي ميداليات\nاذهب إلى مربع الميداليات وتحقق!"; +"20.20.61_text_8" = "تاريخ الانتهاء:%@"; +"20.20.61_text_9" = "دائمًا"; +"20.20.61_text_10" = "مربع الميداليات"; +"20.20.61_text_11" = "قاعة مشاهير الميداليات"; +"20.20.61_text_12" = "المالك القوي"; +"20.20.61_text_13" = "عرض ميدالياته"; +"20.20.61_text_14" = "%@ ميدالية"; +"20.20.61_text_15" = "قم بترقية VIP للحصول على المزيد من فتحات عرض الميداليات!"; +"20.20.61_text_16" = "ميدالياتك ممتلئة!\nاضبطها أولاً ثم أضف المزيد!"; +"20.20.61_text_17" = "نصيحة مقعد الميدالية"; +"20.20.61_text_18" = "قم بالترقية الآن!"; +"20.20.61_text_19" = "بونص"; diff --git a/YuMi/en.lproj/Localizable.strings b/YuMi/en.lproj/Localizable.strings index 9c5305d6..3f8e4b4d 100644 --- a/YuMi/en.lproj/Localizable.strings +++ b/YuMi/en.lproj/Localizable.strings @@ -2549,6 +2549,8 @@ "MvpViewController0" = "Logoff time: %@"; "MvpViewController1" = "%@\n\nPlease contact customer service (WeChat: mxyz2050) for inquiries."; +"MvpViewController1.1" = "%@\n\nPlease contact customer service (WeChat: mxyz2050) for inquiries."; +"MvpViewController1.2" = "Please contact customer service (WeChat: mxyz2050) for inquiries."; "MvpViewController2" = "This account has been logged off"; "MvpViewController3" = "In order to create a safer online environment\nand protect the property safety of you and others,\nplease complete real-name authentication first"; @@ -3991,3 +3993,24 @@ "20.20.59_text_34" = "Submission Successful"; "20.20.59_text_35" = "Sub successful"; "20.20.59_text_36" = "Cancel successful"; + +"20.20.61_text_1" = "My Medals"; +"20.20.61_text_2" = "Medal Wearing"; +"20.20.61_text_3" = "Task Medals"; +"20.20.61_text_4" = "Activity Medals"; +"20.20.61_text_5" = "Glory Medals"; +"20.20.61_text_6" = "You are not wearing any medals"; +"20.20.61_text_7.1" = "You have not received any medals"; +"20.20.61_text_7.2" = "You have not received any medals\nGo to the medal square and check it out!"; +"20.20.61_text_8" = "Expiration time:%@"; +"20.20.61_text_9" = "Forever"; +"20.20.61_text_10" = "Medal Square"; +"20.20.61_text_11" = "Medal Hall of Fame"; +"20.20.61_text_12" = "Powerful Owner"; +"20.20.61_text_13" = "View his medals"; +"20.20.61_text_14" = "%@'s medal"; +"20.20.61_text_15" = "Upgrade VIP to get more medal display slots!"; +"20.20.61_text_16" = "Your medals are full!\nAdjust first and then add more!"; +"20.20.61_text_17" = "Medal Seat Tip"; +"20.20.61_text_18" = "Upgrade now!"; +"20.20.61_text_19" = "Bonus"; diff --git a/YuMi/tr.lproj/Localizable.strings b/YuMi/tr.lproj/Localizable.strings index 979b46e5..e2cb2567 100644 --- a/YuMi/tr.lproj/Localizable.strings +++ b/YuMi/tr.lproj/Localizable.strings @@ -2224,6 +2224,8 @@ "MvpViewController0" = "Oturumu kapatma zamanı: %@"; "MvpViewController1" = "%@\n\nMüşteri hizmetleriyle iletişime geçin (WeChat: mxyz2050) danışın"; +"MvpViewController1.1" = "%@\n\nMüşteri hizmetleriyle iletişime geçin (WeChat: mxyz2050) danışın"; +"MvpViewController1.2" = "Müşteri hizmetleriyle iletişime geçin (WeChat: mxyz2050) danışın"; "MvpViewController2" = "Bu hesap kapatıldı"; "MvpViewController3" = "Daha güvenli bir ağ ortamı oluşturmak ve sizin ve diğerlerinin mülk güvenliğini korumak için\nlütfen önce kimlik doğrulaması yapın"; @@ -3784,3 +3786,24 @@ "20.20.59_text_34" = "Gönderim Başarılı"; "20.20.59_text_35" = "Alt başarılı"; "20.20.59_text_36" = "İptal başarılı"; + +"20.20.61_text_1" = "Madalyalarım"; +"20.20.61_text_2" = "Madalya Takma"; +"20.20.61_text_3" = "Görev Madalyaları"; +"20.20.61_text_4" = "Etkinlik Madalyaları"; +"20.20.61_text_5" = "Şan madalyaları"; +"20.20.61_text_6" = "Hiçbir madalya takmıyorsunuz"; +"20.20.61_text_7.1" = "Hiçbir madalya almadınız"; +"20.20.61_text_7.2" = "Hiçbir madalya almadınız\nMadalya karesine gidin ve kontrol edin!"; +"20.20.61_text_8" = "Son kullanma tarihi:%@"; +"20.20.61_text_9" = "Sonsuza kadar"; +"20.20.61_text_10" = "Madalya Karesi"; +"20.20.61_text_11" = "Madalya Şeref Salonu"; +"20.20.61_text_12" = "Güçlü Sahip"; +"20.20.61_text_13" = "Madalyalarını görüntüle"; +"20.20.61_text_14" = "%@'ın madalyası"; +"20.20.61_text_15" = "Daha fazla madalya sergileme yuvası elde etmek için VIP'yi yükseltin!"; +"20.20.61_text_16" = "Madalyalarınız dolu!\nÖnce ayarlayın ve sonra daha fazlasını ekleyin!"; +"20.20.61_text_17" = "Madalya Koltuğu İpucu"; +"20.20.61_text_18" = "Şimdi yükseltin!"; +"20.20.61_text_19" = "Bonus"; diff --git a/YuMi/zh-Hant.lproj/Localizable.strings b/YuMi/zh-Hant.lproj/Localizable.strings index c2f4f8af..e6024d91 100644 --- a/YuMi/zh-Hant.lproj/Localizable.strings +++ b/YuMi/zh-Hant.lproj/Localizable.strings @@ -2263,7 +2263,8 @@ "LoginBindPhoneViewController2" = "綁定手機號"; "MvpViewController0" = "註銷時間: %@"; -"MvpViewController1" = "%@\n\n請聯繫客服(微信:mxyz2050)咨詢哦"; +"MvpViewController1.1" = "%@\n\n請聯繫客服(微信:mxyz2050)咨詢哦"; +"MvpViewController1.2" = "請聯繫客服(微信:mxyz2050)咨詢哦"; "MvpViewController2" = "該賬號已註銷"; "MvpViewController3" = "為了營造更安全的網絡環境\n保護您和他人的財產安全\n請先進行實名認證"; @@ -3657,17 +3658,23 @@ "20.20.59_text_35" = "訂閱成功"; "20.20.59_text_36" = "取消成功"; -"20.20.61_text_1" = "My Medals"; -"20.20.61_text_2" = "Wearing"; -"20.20.61_text_3" = "Task Medals"; -"20.20.61_text_4" = "Activity Medals"; -"20.20.61_text_5" = "Glory Medals"; -"20.20.61_text_6" = "You are not wearing any medals"; -"20.20.61_text_7" = "You have not received any medals Go to the medal square and check it out!"; -"20.20.61_text_8" = "Expiration time:%@"; -"20.20.61_text_9" = "Forever"; -"20.20.61_text_10" = "Medals Square"; -"20.20.61_text_11" = "Medal Hall of Fame"; -"20.20.61_text_12" = "Powerful Owner"; -"20.20.61_text_13" = "View his medals"; -"20.20.61_text_14" = "%@'s medal"; +"20.20.61_text_1" = "我的獎牌"; +"20.20.61_text_2" = "配戴獎牌"; +"20.20.61_text_3" = "任務獎章"; +"20.20.61_text_4" = "活動獎牌"; +"20.20.61_text_5" = "榮耀勳章"; +"20.20.61_text_6" = "你沒有戴任何獎牌"; +"20.20.61_text_7.1" = "您尚未獲得任何勳章"; +"20.20.61_text_7.2" = "您尚未獲得任何勳章\n去獎牌廣場看看吧!"; +"20.20.61_text_8" = "到期時間:%@"; +"20.20.61_text_9" = "永遠"; +"20.20.61_text_10" = "獎牌廣場"; +"20.20.61_text_11" = "獎章名人堂"; +"20.20.61_text_12" = "強大的拥有者"; +"20.20.61_text_13" = "看他的獎牌"; +"20.20.61_text_14" = "%@的勳章"; +"20.20.61_text_15" = "升級VIP獲得更多勳章展示位!"; +"20.20.61_text_16" = "你的勳章已滿!\n先調整,然後再增加更多!"; +"20.20.61_text_17" = "獎牌座位提示"; +"20.20.61_text_18" = "立即升級!"; +"20.20.61_text_19" = "額外獎勵";