Compare commits
6 Commits
3f97b0293e
...
feature/1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
78152a54d4 | ||
![]() |
e8db7fb24d | ||
![]() |
0438d882d3 | ||
![]() |
48560b5eb6 | ||
![]() |
c6799634e7 | ||
![]() |
9cb71643f1 |
@@ -1,7 +0,0 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
本次对话的上下文已经太长了,我打算关掉并重新开一个新的会话。
|
||||
你有什么想对你的继任者说的,以便它能更好的理解你当前的工作并顺利继续?
|
7
.gitignore
vendored
@@ -82,6 +82,12 @@ iOSInjectionProject/
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.war
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
@@ -145,4 +151,3 @@ iOS/Podfile.lock
|
||||
Podfile.lock
|
||||
*/.DS_Store
|
||||
.DS_Store
|
||||
YuMi.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
|
||||
|
44
.vscode/settings.json
vendored
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"cSpell.ignoreWords": [
|
||||
"DJDKMIMOM",
|
||||
"Strongify",
|
||||
"Weakify",
|
||||
"instancetype",
|
||||
"nonatomic"
|
||||
],
|
||||
"cSpell.words": [
|
||||
"autoreleasepool",
|
||||
"Autoresizing",
|
||||
"BGMP",
|
||||
"Bugly",
|
||||
"Commont",
|
||||
"Contol",
|
||||
"CPSVGA",
|
||||
"Defalut",
|
||||
"erban",
|
||||
"exper",
|
||||
"Headwear",
|
||||
"HWDMP",
|
||||
"ifndef",
|
||||
"Interitem",
|
||||
"kindof",
|
||||
"MAXFLOAT",
|
||||
"Moli",
|
||||
"MSRTL",
|
||||
"NIMSDK",
|
||||
"Nonnull",
|
||||
"NSEC",
|
||||
"NSURL",
|
||||
"objc",
|
||||
"Offical",
|
||||
"Podfile",
|
||||
"Procotol",
|
||||
"QGVAP",
|
||||
"Subview",
|
||||
"subviews",
|
||||
"Superview",
|
||||
"Uids",
|
||||
"XNDJTDD"
|
||||
],
|
||||
"C_Cpp.errorSquiggles": "disabled"
|
||||
}
|
@@ -1,248 +0,0 @@
|
||||
# Banner手势优化实施总结
|
||||
|
||||
## 概述
|
||||
本文档记录了在 `RoomAnimationView.m` 中对 banner 手势系统的优化实施过程。
|
||||
|
||||
## 最新优化内容(2025年1月)
|
||||
|
||||
### 需求描述
|
||||
1. **bannerContainer 手势范围调整**:
|
||||
- 中央宽度 2/3 的位置:保留 swipe 手势
|
||||
- 左右两侧各 1/6 宽度:添加 tap 手势
|
||||
|
||||
2. **tap 手势处理逻辑**:
|
||||
- 检查当前显示的 banner 是否在 tap 位置可以响应事件
|
||||
- 如果可以响应:不处理,让 banner 继续原有逻辑
|
||||
- 如果不能响应:保存 tap 位置点,供后续使用
|
||||
|
||||
### 实施方案
|
||||
|
||||
#### 1. 手势识别器重新设计
|
||||
```objc
|
||||
- (void)addBnnerContainGesture {
|
||||
// 创建独立的手势容器,避免与XPRoomAnimationHitView的hitTest冲突
|
||||
[self insertSubview:self.bannerSwipeGestureContainer aboveSubview:self.bannerContainer];
|
||||
[self insertSubview:self.bannerLeftTapGestureContainer aboveSubview:self.bannerContainer];
|
||||
[self insertSubview:self.bannerRightTapGestureContainer aboveSubview:self.bannerContainer];
|
||||
|
||||
// 设置手势容器的布局约束
|
||||
[self.bannerSwipeGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.mas_equalTo(self.bannerContainer);
|
||||
make.top.bottom.mas_equalTo(self.bannerContainer);
|
||||
make.width.mas_equalTo(self.bannerContainer.mas_width).multipliedBy(2.0/3.0);
|
||||
}];
|
||||
|
||||
[self.bannerLeftTapGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.leading.bottom.mas_equalTo(self.bannerContainer);
|
||||
make.trailing.mas_equalTo(self.bannerSwipeGestureContainer.mas_leading);
|
||||
}];
|
||||
|
||||
[self.bannerRightTapGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.trailing.bottom.mas_equalTo(self.bannerContainer);
|
||||
make.leading.mas_equalTo(self.bannerSwipeGestureContainer.mas_trailing);
|
||||
}];
|
||||
|
||||
// 创建中央区域的 swipe 手势(2/3 宽度)
|
||||
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handleSwipe)];
|
||||
if (isMSRTL()) {
|
||||
swipe.direction = UISwipeGestureRecognizerDirectionRight;
|
||||
} else {
|
||||
swipe.direction = UISwipeGestureRecognizerDirectionLeft;
|
||||
}
|
||||
swipe.delegate = self;
|
||||
|
||||
// 创建左侧区域的 tap 手势(1/6 宽度)
|
||||
UITapGestureRecognizer *leftTap = [[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handleBannerTap:)];
|
||||
leftTap.delegate = self;
|
||||
|
||||
// 创建右侧区域的 tap 手势(1/6 宽度)
|
||||
UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handleBannerTap:)];
|
||||
rightTap.delegate = self;
|
||||
|
||||
// 添加手势识别器到对应的手势容器
|
||||
[self.bannerSwipeGestureContainer addGestureRecognizer:swipe];
|
||||
[self.bannerLeftTapGestureContainer addGestureRecognizer:leftTap];
|
||||
[self.bannerRightTapGestureContainer addGestureRecognizer:rightTap];
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 区域划分逻辑
|
||||
```objc
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
|
||||
CGPoint touchPoint = [touch locationInView:self.bannerContainer];
|
||||
CGFloat containerWidth = self.bannerContainer.bounds.size.width;
|
||||
|
||||
// 计算区域边界
|
||||
CGFloat leftBoundary = containerWidth / 6.0; // 1/6 宽度
|
||||
CGFloat rightBoundary = containerWidth * 5.0 / 6.0; // 5/6 宽度
|
||||
|
||||
if ([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]]) {
|
||||
// Swipe 手势只在中央 2/3 区域生效
|
||||
return touchPoint.x >= leftBoundary && touchPoint.x <= rightBoundary;
|
||||
} else if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
|
||||
// Tap 手势只在左右两侧 1/6 区域生效
|
||||
return touchPoint.x < leftBoundary || touchPoint.x > rightBoundary;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Tap 手势处理逻辑
|
||||
```objc
|
||||
- (void)handleBannerTap:(UITapGestureRecognizer *)tapGesture {
|
||||
CGPoint tapPoint = [tapGesture locationInView:self.bannerContainer];
|
||||
|
||||
// 检查当前显示的 banner 是否在 tap 位置可以响应事件
|
||||
if ([self isPointInBannerInteractiveArea:tapPoint]) {
|
||||
// banner 可以响应,不处理,让 banner 继续原有逻辑
|
||||
NSLog(@"🎯 Banner tap 位置在可交互区域,banner 将处理此事件");
|
||||
return;
|
||||
} else {
|
||||
// banner 不能响应,保存 tap 位置
|
||||
self.savedTapPoint = tapPoint;
|
||||
self.hasSavedTapPoint = YES;
|
||||
NSLog(@"💾 Banner tap 位置不在可交互区域,已保存位置: %@", NSStringFromCGPoint(tapPoint));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Banner 交互区域检查
|
||||
```objc
|
||||
- (BOOL)isPointInBannerInteractiveArea:(CGPoint)point {
|
||||
// 检查当前显示的 banner 是否在指定位置可以响应事件
|
||||
for (UIView *subview in self.bannerContainer.subviews) {
|
||||
if (subview.hidden || subview.alpha <= 0.01) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查点是否在子视图范围内
|
||||
if (CGRectContainsPoint(subview.bounds, point)) {
|
||||
// 检查子视图是否支持用户交互
|
||||
if (subview.userInteractionEnabled) {
|
||||
// 进一步检查子视图是否有可点击的元素
|
||||
CGPoint subviewPoint = [subview convertPoint:point fromView:self.bannerContainer];
|
||||
UIView *hitView = [subview hitTest:subviewPoint withEvent:nil];
|
||||
if (hitView && hitView.userInteractionEnabled) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. 公共接口方法
|
||||
```objc
|
||||
// 获取保存的 tap 位置
|
||||
- (CGPoint)getSavedTapPoint;
|
||||
|
||||
// 检查是否有保存的 tap 位置
|
||||
- (BOOL)hasSavedTapPointAvailable;
|
||||
|
||||
// 清除保存的 tap 位置
|
||||
- (void)clearSavedTapPoint;
|
||||
```
|
||||
|
||||
### 新增属性
|
||||
```objc
|
||||
// Banner 手势相关属性
|
||||
@property(nonatomic, assign) CGPoint savedTapPoint;
|
||||
@property(nonatomic, assign) BOOL hasSavedTapPoint;
|
||||
|
||||
// 手势容器(使用普通UIView避免XPRoomAnimationHitView的hitTest冲突)
|
||||
@property(nonatomic, strong) UIView *bannerSwipeGestureContainer;
|
||||
@property(nonatomic, strong) UIView *bannerLeftTapGestureContainer;
|
||||
@property(nonatomic, strong) UIView *bannerRightTapGestureContainer;
|
||||
```
|
||||
|
||||
### 协议支持
|
||||
- 添加了 `UIGestureRecognizerDelegate` 协议支持
|
||||
- 实现了手势识别器的 delegate 方法
|
||||
|
||||
## 技术特点
|
||||
|
||||
### 1. 精确的区域控制
|
||||
- 使用独立的手势容器精确划分区域
|
||||
- 中央 2/3 区域:swipe 手势容器
|
||||
- 左右两侧各 1/6 区域:tap 手势容器
|
||||
|
||||
### 2. 避免手势冲突
|
||||
- 使用普通 `UIView` 作为手势容器,避免 `XPRoomAnimationHitView` 的 `hitTest` 冲突
|
||||
- 手势容器独立于 banner 内容,确保手势识别不受干扰
|
||||
|
||||
### 3. 智能的事件处理
|
||||
- 检查 banner 是否在 tap 位置可响应
|
||||
- 自动判断是否需要保存 tap 位置
|
||||
- 避免与 banner 原有交互逻辑冲突
|
||||
|
||||
### 4. 灵活的接口设计
|
||||
- 提供公共方法获取保存的 tap 位置
|
||||
- 支持清除保存的位置
|
||||
- 便于外部代码使用
|
||||
|
||||
### 5. 完善的日志记录
|
||||
- 详细记录手势处理过程
|
||||
- 便于调试和问题排查
|
||||
|
||||
## 使用示例
|
||||
|
||||
```objc
|
||||
// 检查是否有保存的 tap 位置
|
||||
if ([roomAnimationView hasSavedTapPointAvailable]) {
|
||||
CGPoint savedPoint = [roomAnimationView getSavedTapPoint];
|
||||
NSLog(@"保存的 tap 位置: %@", NSStringFromCGPoint(savedPoint));
|
||||
|
||||
// 使用保存的位置进行后续处理
|
||||
// ...
|
||||
|
||||
// 清除保存的位置
|
||||
[roomAnimationView clearSavedTapPoint];
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **手势容器设计**:使用普通 `UIView` 作为手势容器,避免 `XPRoomAnimationHitView` 的 `hitTest` 冲突
|
||||
2. **区域划分**:通过独立的视图容器精确划分手势区域,确保手势识别不受干扰
|
||||
3. **交互检查**:通过 `hitTest` 方法检查子视图的实际可交互性
|
||||
4. **内存管理**:及时清除不需要的 tap 位置数据
|
||||
5. **调试支持**:在 DEBUG 模式下为手势容器添加背景色,便于调试区域划分
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **区域划分测试**:
|
||||
- 在中央区域测试 swipe 手势
|
||||
- 在左右两侧测试 tap 手势
|
||||
- 验证手势在错误区域不触发
|
||||
|
||||
2. **交互逻辑测试**:
|
||||
- 在有可交互 banner 的区域 tap
|
||||
- 在无可交互 banner 的区域 tap
|
||||
- 验证 tap 位置的保存和清除
|
||||
|
||||
3. **边界条件测试**:
|
||||
- 测试不同屏幕尺寸下的区域划分
|
||||
- 测试 RTL 语言环境下的手势方向
|
||||
- 测试多个 banner 同时显示的情况
|
||||
|
||||
## 总结
|
||||
|
||||
本次优化成功实现了:
|
||||
- ✅ bannerContainer 手势范围的精确划分
|
||||
- ✅ 智能的 tap 手势处理逻辑
|
||||
- ✅ 灵活的 tap 位置保存机制
|
||||
- ✅ 完善的公共接口设计
|
||||
- ✅ 与现有代码的良好兼容性
|
||||
- ✅ 解决了 XPRoomAnimationHitView 的手势冲突问题
|
||||
|
||||
### 关键改进
|
||||
1. **避免手势冲突**:使用普通 `UIView` 作为手势容器,避免 `XPRoomAnimationHitView` 的 `hitTest` 方法干扰
|
||||
2. **精确区域控制**:通过独立的视图容器实现精确的手势区域划分
|
||||
3. **调试友好**:在 DEBUG 模式下为手势容器添加背景色,便于调试
|
||||
|
||||
该方案既满足了新的功能需求,又解决了潜在的手势冲突问题,保持了代码的可维护性和扩展性。
|
@@ -1,471 +0,0 @@
|
||||
# 新Banner组件架构设计
|
||||
|
||||
## 设计概述
|
||||
|
||||
基于对现有7种Banner组件的深度分析,设计一套统一的Banner组件架构,包含父类、子类、数据模型和用户反馈机制。
|
||||
|
||||
## 现有组件分析总结
|
||||
|
||||
### 共同UI结构模式
|
||||
|
||||
| 组件 | 背景 | 头像 | 标题/内容 | 礼物图标 | 操作按钮 | 动画效果 |
|
||||
|------|------|------|-----------|----------|----------|----------|
|
||||
| RoomHighValueGiftBannerAnimation | ✓ | ✓ | ✓ | ✓ | ✓ | SVGA |
|
||||
| CPGiftBanner | ✓ | ✓✓(双人) | ✓ | ✓ | - | POP |
|
||||
| BravoGiftBannerView | ✓ | ✓ | ✓ | ✓ | - | SVGA |
|
||||
| LuckyPackageBannerView | ✓ | ✓ | ✓ | - | ✓ | POP |
|
||||
| LuckyGiftWinningBannerView | ✓ | ✓ | ✓ | - | ✓ | POP |
|
||||
| GameUniversalBannerView | ✓ | ✓ | ✓ | ✓ | ✓ | SVGA |
|
||||
| PIUniversalBannerView | ✓ | - | ✓ | - | ✓ | SVGA |
|
||||
|
||||
### 共同数据模式
|
||||
|
||||
- AttachmentModel作为数据源
|
||||
- 专用ViewModel进行数据解析
|
||||
- 完成回调机制
|
||||
- 用户交互跳转
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 1. 基础父类设计
|
||||
|
||||
```objc
|
||||
// YMBaseBannerView.h
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "YMBannerDataProtocol.h"
|
||||
#import "YMBannerDelegate.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class AttachmentModel, YMBaseBannerViewModel;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, YMBannerType) {
|
||||
YMBannerTypeHighValueGift, // 高价值礼物
|
||||
YMBannerTypeCPGift, // CP礼物
|
||||
YMBannerTypeBravoGift, // Bravo超级礼物
|
||||
YMBannerTypeLuckyPackage, // 幸运红包
|
||||
YMBannerTypeLuckyWinning, // 幸运中奖
|
||||
YMBannerTypeGameUniversal, // 通用游戏
|
||||
YMBannerTypeUniversal // 通用飘屏
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, YMBannerAnimationType) {
|
||||
YMBannerAnimationTypeSlide, // 滑动动画
|
||||
YMBannerAnimationTypeFade, // 淡入淡出
|
||||
YMBannerAnimationTypeBounce, // 弹跳效果
|
||||
YMBannerAnimationTypeCustom // 自定义动画
|
||||
};
|
||||
|
||||
@interface YMBaseBannerView : UIView
|
||||
|
||||
#pragma mark - 核心属性
|
||||
@property (nonatomic, assign, readonly) YMBannerType bannerType;
|
||||
@property (nonatomic, strong) AttachmentModel *attachment;
|
||||
@property (nonatomic, strong) YMBaseBannerViewModel *viewModel;
|
||||
@property (nonatomic, weak) id<YMBannerDelegate> delegate;
|
||||
|
||||
#pragma mark - UI组件 (子类可选择使用)
|
||||
@property (nonatomic, strong, readonly) UIView *containerView;
|
||||
@property (nonatomic, strong, readonly) UIImageView *backgroundImageView;
|
||||
@property (nonatomic, strong, readonly) NetImageView *avatarImageView;
|
||||
@property (nonatomic, strong, readonly) NetImageView *secondAvatarImageView; // CP双头像
|
||||
@property (nonatomic, strong, readonly) UILabel *titleLabel;
|
||||
@property (nonatomic, strong, readonly) UILabel *subtitleLabel;
|
||||
@property (nonatomic, strong, readonly) UILabel *contentLabel;
|
||||
@property (nonatomic, strong, readonly) NetImageView *iconImageView;
|
||||
@property (nonatomic, strong, readonly) UIButton *actionButton;
|
||||
@property (nonatomic, strong, readonly) SVGAImageView *svgaView;
|
||||
|
||||
#pragma mark - 动画配置
|
||||
@property (nonatomic, assign) YMBannerAnimationType animationType;
|
||||
@property (nonatomic, assign) CGFloat showDuration;
|
||||
@property (nonatomic, assign) CGFloat stayDuration;
|
||||
@property (nonatomic, assign) CGFloat hideDuration;
|
||||
@property (nonatomic, assign) BOOL enableSwipeGesture;
|
||||
|
||||
#pragma mark - 回调
|
||||
@property (nonatomic, copy) void(^onDisplayComplete)(void);
|
||||
@property (nonatomic, copy) void(^onUserTap)(YMBaseBannerView *banner);
|
||||
@property (nonatomic, copy) void(^onActionTap)(YMBaseBannerView *banner);
|
||||
@property (nonatomic, copy) void(^onDismiss)(YMBaseBannerView *banner);
|
||||
|
||||
#pragma mark - 状态
|
||||
@property (nonatomic, assign, readonly) BOOL isDisplaying;
|
||||
@property (nonatomic, assign, readonly) BOOL isDismissed;
|
||||
|
||||
#pragma mark - 类方法
|
||||
+ (instancetype)bannerWithAttachment:(AttachmentModel *)attachment;
|
||||
+ (void)displayInView:(UIView *)superView
|
||||
attachment:(AttachmentModel *)attachment
|
||||
complete:(void(^)(void))complete;
|
||||
|
||||
#pragma mark - 实例方法
|
||||
- (instancetype)initWithBannerType:(YMBannerType)type;
|
||||
- (void)configureWithAttachment:(AttachmentModel *)attachment;
|
||||
- (void)displayInView:(UIView *)superView;
|
||||
- (void)dismissWithAnimation:(BOOL)animated;
|
||||
|
||||
#pragma mark - 子类重写方法
|
||||
- (Class)viewModelClass; // 返回对应的ViewModel类
|
||||
- (void)setupUIComponents; // 设置UI组件
|
||||
- (void)layoutUIComponents; // 布局UI组件
|
||||
- (void)configureWithViewModel:(YMBaseBannerViewModel *)viewModel; // 配置数据
|
||||
- (void)performShowAnimation; // 执行显示动画
|
||||
- (void)performHideAnimation; // 执行隐藏动画
|
||||
- (void)handleUserTap; // 处理用户点击
|
||||
- (void)handleActionTap; // 处理操作按钮点击
|
||||
|
||||
#pragma mark - 用户反馈
|
||||
- (void)reportDisplayEvent; // 上报展示事件
|
||||
- (void)reportClickEvent:(NSString *)action; // 上报点击事件
|
||||
- (void)reportDismissEvent:(NSString *)reason; // 上报消失事件
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
```
|
||||
|
||||
### 2. 基础数据模型
|
||||
|
||||
```objc
|
||||
// YMBaseBannerViewModel.h
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PIBaseModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YMBaseBannerViewModel : PIBaseModel
|
||||
|
||||
#pragma mark - 基础信息
|
||||
@property (nonatomic, assign) NSInteger roomUid;
|
||||
@property (nonatomic, assign) NSInteger targetRoomUid;
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) NSString *subtitle;
|
||||
@property (nonatomic, copy) NSString *content;
|
||||
|
||||
#pragma mark - 用户信息
|
||||
@property (nonatomic, copy) NSString *senderNick;
|
||||
@property (nonatomic, copy) NSString *senderAvatar;
|
||||
@property (nonatomic, assign) NSInteger senderUid;
|
||||
@property (nonatomic, copy) NSString *receiverNick;
|
||||
@property (nonatomic, copy) NSString *receiverAvatar;
|
||||
@property (nonatomic, assign) NSInteger receiverUid;
|
||||
|
||||
#pragma mark - 视觉资源
|
||||
@property (nonatomic, copy) NSString *backgroundImageUrl;
|
||||
@property (nonatomic, copy) NSString *iconImageUrl;
|
||||
@property (nonatomic, copy) NSString *svgaUrl;
|
||||
|
||||
#pragma mark - 交互配置
|
||||
@property (nonatomic, copy) NSString *actionText;
|
||||
@property (nonatomic, copy) NSString *skipUrl;
|
||||
@property (nonatomic, assign) BOOL enableClick;
|
||||
@property (nonatomic, assign) BOOL enableAction;
|
||||
|
||||
#pragma mark - 埋点数据
|
||||
@property (nonatomic, copy) NSString *eventType;
|
||||
@property (nonatomic, strong) NSDictionary *trackingData;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
```
|
||||
|
||||
### 3. 代理协议设计
|
||||
|
||||
```objc
|
||||
// YMBannerDelegate.h
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YMBaseBannerView;
|
||||
|
||||
@protocol YMBannerDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
#pragma mark - 生命周期回调
|
||||
- (void)bannerWillDisplay:(YMBaseBannerView *)banner;
|
||||
- (void)bannerDidDisplay:(YMBaseBannerView *)banner;
|
||||
- (void)bannerWillDismiss:(YMBaseBannerView *)banner;
|
||||
- (void)bannerDidDismiss:(YMBaseBannerView *)banner;
|
||||
|
||||
#pragma mark - 交互回调
|
||||
- (void)banner:(YMBaseBannerView *)banner didTapWithAction:(NSString *)action;
|
||||
- (void)banner:(YMBaseBannerView *)banner willNavigateToRoom:(NSInteger)roomUid;
|
||||
- (void)banner:(YMBaseBannerView *)banner willOpenURL:(NSString *)url;
|
||||
|
||||
#pragma mark - 数据回调
|
||||
- (void)banner:(YMBaseBannerView *)banner didReportEvent:(NSString *)event data:(NSDictionary *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
```
|
||||
|
||||
## 子类设计
|
||||
|
||||
### 1. 高价值礼物Banner
|
||||
|
||||
```objc
|
||||
// YMHighValueGiftBannerView.h
|
||||
#import "YMBaseBannerView.h"
|
||||
|
||||
@interface YMHighValueGiftBannerView : YMBaseBannerView
|
||||
|
||||
@property (nonatomic, strong, readonly) UILabel *giftNameLabel;
|
||||
@property (nonatomic, strong, readonly) UILabel *giftCountLabel;
|
||||
@property (nonatomic, strong, readonly) MarqueeLabel *senderScrollLabel;
|
||||
@property (nonatomic, strong, readonly) MarqueeLabel *roomNameScrollLabel;
|
||||
|
||||
@end
|
||||
|
||||
// YMHighValueGiftBannerViewModel.h
|
||||
@interface YMHighValueGiftBannerViewModel : YMBaseBannerViewModel
|
||||
|
||||
@property (nonatomic, copy) NSString *giftName;
|
||||
@property (nonatomic, assign) NSInteger giftCount;
|
||||
@property (nonatomic, copy) NSString *giftImageUrl;
|
||||
@property (nonatomic, assign) NSInteger bgLevel;
|
||||
@property (nonatomic, copy) NSString *roomTitle;
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### 2. CP礼物Banner
|
||||
|
||||
```objc
|
||||
// YMCPGiftBannerView.h
|
||||
#import "YMBaseBannerView.h"
|
||||
|
||||
@interface YMCPGiftBannerView : YMBaseBannerView
|
||||
|
||||
@property (nonatomic, strong, readonly) UIStackView *cpStackView;
|
||||
@property (nonatomic, strong, readonly) UILabel *relationLabel;
|
||||
|
||||
@end
|
||||
|
||||
// YMCPGiftBannerViewModel.h
|
||||
@interface YMCPGiftBannerViewModel : YMBaseBannerViewModel
|
||||
|
||||
@property (nonatomic, copy) NSString *giftImageUrl;
|
||||
@property (nonatomic, copy) NSString *relationText;
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### 3. 通用游戏Banner
|
||||
|
||||
```objc
|
||||
// YMGameUniversalBannerView.h
|
||||
#import "YMBaseBannerView.h"
|
||||
|
||||
@interface YMGameUniversalBannerView : YMBaseBannerView
|
||||
|
||||
@property (nonatomic, strong, readonly) NetImageView *gameIconView;
|
||||
@property (nonatomic, assign) NSInteger gameID;
|
||||
|
||||
@end
|
||||
|
||||
// YMGameUniversalBannerViewModel.h
|
||||
@interface YMGameUniversalBannerViewModel : YMBaseBannerViewModel
|
||||
|
||||
@property (nonatomic, copy) NSString *gameIconUrl;
|
||||
@property (nonatomic, assign) NSInteger gameID;
|
||||
@property (nonatomic, copy) NSString *gameTitle;
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
## 用户反馈机制设计
|
||||
|
||||
### 1. 反馈管理器
|
||||
|
||||
```objc
|
||||
// YMBannerFeedbackManager.h
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, YMBannerFeedbackType) {
|
||||
YMBannerFeedbackTypeDisplay, // 展示
|
||||
YMBannerFeedbackTypeClick, // 点击
|
||||
YMBannerFeedbackTypeAction, // 操作
|
||||
YMBannerFeedbackTypeDismiss, // 消失
|
||||
YMBannerFeedbackTypeError // 错误
|
||||
};
|
||||
|
||||
@interface YMBannerFeedbackManager : NSObject
|
||||
|
||||
+ (instancetype)shared;
|
||||
|
||||
#pragma mark - 事件上报
|
||||
- (void)reportBannerEvent:(YMBannerFeedbackType)type
|
||||
banner:(YMBaseBannerView *)banner
|
||||
data:(NSDictionary *)data;
|
||||
|
||||
#pragma mark - 性能监控
|
||||
- (void)startPerformanceMonitoring:(YMBaseBannerView *)banner;
|
||||
- (void)endPerformanceMonitoring:(YMBaseBannerView *)banner;
|
||||
|
||||
#pragma mark - 错误收集
|
||||
- (void)reportError:(NSError *)error
|
||||
banner:(YMBaseBannerView *)banner
|
||||
context:(NSDictionary *)context;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
```
|
||||
|
||||
### 2. 埋点事件定义
|
||||
|
||||
```objc
|
||||
// YMBannerTrackingEvents.h
|
||||
extern NSString * const kYMBannerEventDisplay; // banner_display
|
||||
extern NSString * const kYMBannerEventClick; // banner_click
|
||||
extern NSString * const kYMBannerEventAction; // banner_action
|
||||
extern NSString * const kYMBannerEventDismiss; // banner_dismiss
|
||||
extern NSString * const kYMBannerEventLoadStart; // banner_load_start
|
||||
extern NSString * const kYMBannerEventLoadEnd; // banner_load_end
|
||||
extern NSString * const kYMBannerEventError; // banner_error
|
||||
|
||||
// 埋点参数Key
|
||||
extern NSString * const kYMBannerTrackingKeyType; // banner_type
|
||||
extern NSString * const kYMBannerTrackingKeyRoomUid; // room_uid
|
||||
extern NSString * const kYMBannerTrackingKeyDuration; // duration
|
||||
extern NSString * const kYMBannerTrackingKeyAction; // action
|
||||
extern NSString * const kYMBannerTrackingKeyReason; // reason
|
||||
```
|
||||
|
||||
## 上下文调整说明
|
||||
|
||||
### 1. RoomAnimationView.m 调整
|
||||
|
||||
#### 1.1 替换现有Banner创建逻辑
|
||||
|
||||
```objc
|
||||
// 原代码
|
||||
- (void)playRoomGiftBanner:(AttachmentModel *)obj {
|
||||
[RoomHighValueGiftBannerAnimation display:self.bannerContainer
|
||||
with:obj
|
||||
complete:^{
|
||||
self.isRoomBannerV2Displaying = NO;
|
||||
[self processNextRoomEffectAttachment];
|
||||
}];
|
||||
}
|
||||
|
||||
// 新代码
|
||||
- (void)playRoomGiftBanner:(AttachmentModel *)obj {
|
||||
YMHighValueGiftBannerView *banner = [YMHighValueGiftBannerView bannerWithAttachment:obj];
|
||||
banner.delegate = self;
|
||||
banner.onDisplayComplete = ^{
|
||||
self.isRoomBannerV2Displaying = NO;
|
||||
[self processNextRoomEffectAttachment];
|
||||
};
|
||||
[banner displayInView:self.bannerContainer];
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 添加代理实现
|
||||
|
||||
```objc
|
||||
#pragma mark - YMBannerDelegate
|
||||
- (void)banner:(YMBaseBannerView *)banner didReportEvent:(NSString *)event data:(NSDictionary *)data {
|
||||
[[YMBannerFeedbackManager shared] reportBannerEvent:YMBannerFeedbackTypeDisplay
|
||||
banner:banner
|
||||
data:data];
|
||||
}
|
||||
|
||||
- (void)banner:(YMBaseBannerView *)banner willNavigateToRoom:(NSInteger)roomUid {
|
||||
// 处理房间跳转逻辑
|
||||
RoomInfoModel *currentRoom = self.hostDelegate.getRoomInfo;
|
||||
if (currentRoom.uid != roomUid) {
|
||||
// 执行跨房间跳转
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 项目依赖调整
|
||||
|
||||
#### 2.1 新增文件结构
|
||||
|
||||
```YuMi/Modules/YMRoom/View/Banner/
|
||||
├── Base/
|
||||
│ ├── YMBaseBannerView.h/.m
|
||||
│ ├── YMBaseBannerViewModel.h/.m
|
||||
│ └── YMBannerDelegate.h
|
||||
├── Subclasses/
|
||||
│ ├── YMHighValueGiftBannerView.h/.m
|
||||
│ ├── YMCPGiftBannerView.h/.m
|
||||
│ ├── YMBravoGiftBannerView.h/.m
|
||||
│ ├── YMGameUniversalBannerView.h/.m
|
||||
│ └── YMLuckyPackageBannerView.h/.m
|
||||
├── Manager/
|
||||
│ └── YMBannerFeedbackManager.h/.m
|
||||
└── Constants/
|
||||
└── YMBannerTrackingEvents.h/.m
|
||||
```
|
||||
|
||||
#### 2.2 Podfile依赖更新
|
||||
|
||||
```ruby
|
||||
# 确保动画库版本兼容
|
||||
pod 'pop', '~> 1.0'
|
||||
pod 'SVGAPlayer', '~> 2.3'
|
||||
|
||||
# 新增性能监控
|
||||
pod 'YMPerformanceMonitor' # 如果有自定义性能监控库
|
||||
```
|
||||
|
||||
### 3. 配置文件调整
|
||||
|
||||
#### 3.1 添加Banner配置
|
||||
|
||||
```objc
|
||||
// YMBannerConfig.h
|
||||
@interface YMBannerConfig : NSObject
|
||||
|
||||
@property (nonatomic, assign) BOOL enablePerformanceMonitoring;
|
||||
@property (nonatomic, assign) BOOL enableErrorReporting;
|
||||
@property (nonatomic, assign) CGFloat defaultShowDuration;
|
||||
@property (nonatomic, assign) CGFloat defaultStayDuration;
|
||||
@property (nonatomic, assign) CGFloat defaultHideDuration;
|
||||
|
||||
+ (instancetype)shared;
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### 4. 迁移策略
|
||||
|
||||
#### 4.1 Phase 1: 基础架构
|
||||
|
||||
- 创建基类和协议
|
||||
- 实现反馈管理器
|
||||
- 设置基础配置
|
||||
|
||||
#### 4.2 Phase 2: 渐进迁移
|
||||
|
||||
- 优先迁移使用频率最高的Banner
|
||||
- 保持向后兼容性
|
||||
- 添加单元测试
|
||||
|
||||
#### 4.3 Phase 3: 优化完善
|
||||
|
||||
- 移除旧代码
|
||||
- 性能优化
|
||||
- 文档完善
|
||||
|
||||
## 总结
|
||||
|
||||
这套新的Banner组件架构具备以下优势:
|
||||
|
||||
1. **统一性**: 提供一致的API和行为模式
|
||||
2. **扩展性**: 易于添加新的Banner类型
|
||||
3. **可维护性**: 集中管理动画、埋点和反馈
|
||||
4. **性能优化**: 统一的资源管理和内存优化
|
||||
5. **用户体验**: 标准化的交互和反馈机制
|
||||
|
||||
通过这套架构,可以显著提升Banner组件的开发效率和用户体验质量。
|
@@ -1,80 +0,0 @@
|
||||
# 飘屏组件分析文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细分析了项目中在 `RoomAnimationView.m` 的 `bannerContainer` 中展示的所有飘屏类型及其触发逻辑。
|
||||
|
||||
## 飘屏展示机制
|
||||
|
||||
- **展示容器**: `bannerContainer` (XPRoomAnimationHitView)
|
||||
- **位置**: 视图层级最顶层,距离顶部导航栏高度
|
||||
- **尺寸**: 屏幕宽度 × 180px 高度
|
||||
- **管理机制**: 统一队列管理,按优先级顺序播放
|
||||
|
||||
## 飘屏类型详细表格
|
||||
|
||||
| 序号 | 飘屏名称 | 类名 | 触发消息类型 | 处理方法 | 业务场景 | 功能描述 |
|
||||
|-----|---------|------|-------------|---------|----------|---------|
|
||||
| 1 | 高价值礼物飘屏 | RoomHighValueGiftBannerAnimation | Custom_Message_Sub_Gift_ChannelNotify | playRoomGiftBanner: | 礼物系统 | 展示高价值礼物的全服广播 |
|
||||
| 2 | CP礼物飘屏 | CPGiftBanner | Custom_Message_Sub_CP_Gift | playCPGiftBanner: | CP系统 | 展示CP相关礼物特效 |
|
||||
| 3 | Bravo超级礼物飘屏 | BravoGiftBannerView | Custom_Message_Sub_Super_Gift_Banner | playBroveBanner: | 超级礼物 | 展示Bravo超级礼物效果 |
|
||||
| 4 | 幸运红包飘屏 | LuckyPackageBannerView | Custom_Message_Sub_LuckyPackage | playLuckyPackageBanner: | 红包系统 | 展示房间红包,支持跨房间跳转 |
|
||||
| 5 | 幸运礼物中奖飘屏 | LuckyGiftWinningBannerView | Custom_Message_Sub_Super_Gift_Winning_Coins_ALL_Room | playLuckyWinningBanner: | 中奖系统 | 展示全服中奖信息 |
|
||||
| 6 | 通用游戏飘屏 | GameUniversalBannerView | Custom_Message_Sub_General_Floating_Screen_One_Room<br/>Custom_Message_Sub_General_Floating_Screen_All_Room | playGameBanner: | 游戏系统 | 展示游戏相关飘屏,支持跳转 |
|
||||
| 7 | 塔罗飘屏 | XPRoomTarrowBannerView | Custom_Message_Sub_Tarot_Advanced<br/>Custom_Message_Sub_Tarot_Intermediate | createTarotBannerAnimation: | 塔罗活动 | 展示塔罗相关活动信息 |
|
||||
| 8 | 星厨房飘屏 | XPRoomStarKitchenBannerView | Custom_Message_Sub_Star_Kitchen_FullScreen | createStarKitchenBannerAnimation: | 厨房活动 | 展示星厨房活动 |
|
||||
| 9 | 夺宝精灵飘屏 | - | Custom_Message_Sub_Treasure_Fairy_Draw_Gift_L4<br/>Custom_Message_Sub_Treasure_Fairy_Draw_Gift_L5<br/>Custom_Message_Sub_Treasure_Fairy_Convert_L1/L2/L3 | createTreasureFairyBannerAnimation: | 夺宝活动 | 展示夺宝精灵高等级奖励 |
|
||||
| 10 | 主播小时榜飘屏 | XPRoomAnchorRankBannerView | Custom_Message_Sub_Anchor_Hour_Rank | 创建主播排行榜飘屏 | 排行榜 | 展示主播小时榜信息 |
|
||||
| 11 | 通用H5飘屏 | XPRoomTarrowBannerView | Custom_Message_Sub_Common_H5_Novice<br/>Custom_Message_Sub_Common_H5_Advanced | createCommonH5BannerAnimation: | H5活动 | 展示通用H5活动飘屏 |
|
||||
| 12 | 通用飘屏 | PIUniversalBannerView | 多种消息类型 | createGeneralFloatingScreenAnimation: | 通用展示 | 提供通用飘屏展示能力 |
|
||||
|
||||
## 队列管理机制
|
||||
|
||||
### 核心属性
|
||||
|
||||
- **队列数组**: `roomBannertModelsQueueV2` (NSMutableArray)
|
||||
- **状态标记**: `isRoomBannerV2Displaying` (BOOL)
|
||||
|
||||
### 处理流程
|
||||
|
||||
1. **消息接收**: 调用 `inserBannerModelToQueue:` 将消息加入队列
|
||||
2. **优先级排序**: 调用 `sortBannerQueue` 按消息类型的second值排序(数值越小优先级越高)
|
||||
3. **顺序播放**: 调用 `processNextRoomEffectAttachment` 逐个播放
|
||||
4. **播放完成**: 每个飘屏播放完成后,设置 `isRoomBannerV2Displaying = NO` 并处理下一个
|
||||
|
||||
### 优先级规则
|
||||
|
||||
飘屏按 `AttachmentModel.second` 值进行排序,确保重要消息优先展示。
|
||||
|
||||
## 消息类型分类
|
||||
|
||||
### 礼物相关
|
||||
|
||||
- Custom_Message_Sub_Gift_ChannelNotify (32)
|
||||
- Custom_Message_Sub_CP_Gift
|
||||
- Custom_Message_Sub_Super_Gift_Banner (1066)
|
||||
|
||||
### 红包/福袋相关
|
||||
|
||||
- Custom_Message_Sub_LuckyPackage (607)
|
||||
- Custom_Message_Sub_Super_Gift_Winning_Coins_ALL_Room (1063)
|
||||
|
||||
### 游戏相关
|
||||
|
||||
- Custom_Message_Sub_General_Floating_Screen_One_Room (1071)
|
||||
- Custom_Message_Sub_General_Floating_Screen_All_Room (1072)
|
||||
|
||||
### 活动相关
|
||||
|
||||
- Custom_Message_Sub_Tarot_Advanced (714)
|
||||
- Custom_Message_Sub_Tarot_Intermediate (713)
|
||||
- Custom_Message_Sub_Star_Kitchen_FullScreen (1042)
|
||||
- Custom_Message_Sub_Treasure_Fairy_* (9700系列)
|
||||
|
||||
### 排行榜相关
|
||||
|
||||
- Custom_Message_Sub_Anchor_Hour_Rank (891)
|
||||
|
||||
### 通用相关
|
||||
|
||||
- Custom_Message_Sub_Common_H5_* (1100系列)
|
203
DOC/飘屏组件抽象分析.md
@@ -1,203 +0,0 @@
|
||||
# 飘屏组件抽象分析
|
||||
|
||||
## 分析概述
|
||||
|
||||
基于对项目中12种飘屏组件的深入分析,发现这些组件存在高度相似的设计模式和实现结构,具备抽象出统一父类的条件。
|
||||
|
||||
## 现状分析
|
||||
|
||||
### 共同特征
|
||||
|
||||
经过代码分析,发现所有飘屏组件都具有以下共同特征:
|
||||
|
||||
#### 1. 统一的类方法签名模式
|
||||
|
||||
大部分Banner类都采用了相似的类方法签名:
|
||||
|
||||
``` objc
|
||||
+ (void)display:(UIView *)superView
|
||||
with:(AttachmentModel *)attachment
|
||||
complete:(void(^)(void))complete;
|
||||
|
||||
// 或者带房间信息的版本
|
||||
+ (void)display:(UIView *)superView
|
||||
inRoomUid:(NSInteger)roomUid
|
||||
with:(AttachmentModel *)attachment
|
||||
complete:(void(^)(void))complete
|
||||
exitCurrentRoom:(void(^)(void))exit;
|
||||
```
|
||||
|
||||
#### 2. 共同的内部属性结构
|
||||
|
||||
```objc
|
||||
@property (nonatomic, strong) SomeViewModel *model; // 数据模型
|
||||
@property (nonatomic, strong) UIImageView *backgroundImageView; // 背景图片
|
||||
@property (nonatomic, copy) void(^completeDisplay)(void); // 完成回调
|
||||
@property (nonatomic, copy) void(^exitCurrentRoom)(void); // 退房回调
|
||||
@property (nonatomic, assign) NSInteger currentRoomUid; // 当前房间ID
|
||||
```
|
||||
|
||||
#### 3. 相似的动画流程
|
||||
|
||||
- 从屏幕右侧滑入 (x = KScreenWidth)
|
||||
- 短暂停留展示
|
||||
- 滑出屏幕或淡出
|
||||
- 执行完成回调
|
||||
|
||||
#### 4. 统一的数据处理模式
|
||||
|
||||
- 都依赖 `AttachmentModel` 作为数据源
|
||||
- 都创建对应的ViewModel进行数据解析
|
||||
- 都继承自 `PIBaseModel`
|
||||
|
||||
#### 5. 相似的UI组件
|
||||
|
||||
- 背景图片视图
|
||||
- 用户头像
|
||||
- 文本标签
|
||||
- 可选的SVGA动画视图
|
||||
- 可选的交互按钮
|
||||
|
||||
## 抽象方案设计
|
||||
|
||||
### 建议的基类结构: `BaseRoomBannerView`
|
||||
|
||||
```objc
|
||||
@interface BaseRoomBannerView : UIView
|
||||
|
||||
#pragma mark - 核心属性
|
||||
@property (nonatomic, strong) AttachmentModel *attachment;
|
||||
@property (nonatomic, strong) PIBaseModel *viewModel;
|
||||
@property (nonatomic, assign) NSInteger currentRoomUid;
|
||||
@property (nonatomic, assign) NSInteger targetRoomUid;
|
||||
|
||||
#pragma mark - UI组件
|
||||
@property (nonatomic, strong) UIView *containerView;
|
||||
@property (nonatomic, strong) UIImageView *backgroundImageView;
|
||||
@property (nonatomic, strong) NetImageView *avatarImageView;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
@property (nonatomic, strong) UILabel *subTitleLabel;
|
||||
@property (nonatomic, strong) UIButton *actionButton;
|
||||
@property (nonatomic, strong) SVGAImageView *svgaView;
|
||||
|
||||
#pragma mark - 动画配置
|
||||
@property (nonatomic, assign) CGFloat showDuration;
|
||||
@property (nonatomic, assign) CGFloat stayDuration;
|
||||
@property (nonatomic, assign) CGFloat hideDuration;
|
||||
@property (nonatomic, assign) BOOL useSlideAnimation;
|
||||
@property (nonatomic, assign) BOOL useFadeAnimation;
|
||||
|
||||
#pragma mark - 回调
|
||||
@property (nonatomic, copy) void(^completeDisplay)(void);
|
||||
@property (nonatomic, copy) void(^exitCurrentRoom)(void);
|
||||
@property (nonatomic, copy) void(^didTapBanner)(NSInteger roomID);
|
||||
|
||||
#pragma mark - 类方法
|
||||
+ (instancetype)createWithAttachment:(AttachmentModel *)attachment
|
||||
inRoomUid:(NSInteger)roomUid
|
||||
complete:(void(^)(void))complete
|
||||
exitCurrentRoom:(void(^)(void))exit;
|
||||
|
||||
+ (void)display:(UIView *)superView
|
||||
with:(AttachmentModel *)attachment
|
||||
complete:(void(^)(void))complete;
|
||||
|
||||
+ (void)display:(UIView *)superView
|
||||
inRoomUid:(NSInteger)roomUid
|
||||
with:(AttachmentModel *)attachment
|
||||
complete:(void(^)(void))complete
|
||||
exitCurrentRoom:(void(^)(void))exit;
|
||||
|
||||
#pragma mark - 实例方法
|
||||
- (void)setupWithAttachment:(AttachmentModel *)attachment;
|
||||
- (void)showInSuperView:(UIView *)superView;
|
||||
- (void)performShowAnimation;
|
||||
- (void)performHideAnimation;
|
||||
|
||||
#pragma mark - 子类重写方法
|
||||
- (Class)viewModelClass; // 返回对应的ViewModel类
|
||||
- (void)setupUI; // 设置UI布局
|
||||
- (void)configureWithModel:(PIBaseModel *)model; // 配置数据
|
||||
- (void)customizeAnimation; // 自定义动画
|
||||
- (void)handleTapAction; // 处理点击事件
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### 抽象优势分析
|
||||
|
||||
#### 1. 代码复用率提升
|
||||
|
||||
- 消除重复的动画逻辑
|
||||
- 统一的数据处理流程
|
||||
- 共享的UI组件管理
|
||||
|
||||
#### 2. 维护成本降低
|
||||
|
||||
- 集中管理动画参数
|
||||
- 统一的回调处理机制
|
||||
- 标准化的生命周期管理
|
||||
|
||||
#### 3. 扩展性增强
|
||||
|
||||
- 新增飘屏类型只需继承基类
|
||||
- 便于添加通用功能(如埋点、性能监控)
|
||||
- 支持主题切换等全局功能
|
||||
|
||||
#### 4. 一致性保证
|
||||
|
||||
- 统一的动画效果和时长
|
||||
- 标准化的交互行为
|
||||
- 一致的视觉表现
|
||||
|
||||
### 实现策略
|
||||
|
||||
#### 阶段一:创建基类
|
||||
|
||||
1. 提取共同属性和方法
|
||||
2. 实现通用动画逻辑
|
||||
3. 定义子类接口规范
|
||||
|
||||
#### 阶段二:重构现有类
|
||||
|
||||
1. 逐步迁移现有Banner类继承基类
|
||||
2. 移除重复代码
|
||||
3. 保持向后兼容
|
||||
|
||||
#### 阶段三:优化和扩展
|
||||
|
||||
1. 添加性能监控
|
||||
2. 实现主题支持
|
||||
3. 统一埋点逻辑
|
||||
|
||||
### 潜在挑战
|
||||
|
||||
#### 1. 兼容性问题
|
||||
|
||||
- 现有代码的重构风险
|
||||
- 不同Banner的特殊需求差异
|
||||
- 第三方依赖的适配
|
||||
|
||||
#### 2. 性能考虑
|
||||
|
||||
- 基类功能过于复杂可能影响性能
|
||||
- 内存占用的优化
|
||||
- 动画性能的平衡
|
||||
|
||||
#### 3. 维护复杂度
|
||||
|
||||
- 基类变更可能影响所有子类
|
||||
- 需要详细的文档和测试
|
||||
- 团队学习成本
|
||||
|
||||
## 结论
|
||||
|
||||
**建议进行抽象**:项目中的飘屏组件具有高度的相似性,抽象出基类能够显著提升代码质量和开发效率。建议采用渐进式重构策略,先创建基类和接口规范,再逐步迁移现有实现,确保系统稳定性的同时实现架构优化。
|
||||
|
||||
## 优先级建议
|
||||
|
||||
1. **高优先级**:创建 `BaseRoomBannerView` 基类
|
||||
2. **中优先级**:重构使用频率最高的Banner类
|
||||
3. **低优先级**:添加扩展功能和优化
|
||||
|
||||
通过这种抽象设计,能够在保持现有功能完整性的同时,为未来的功能扩展和维护奠定良好的架构基础。
|
@@ -1,434 +0,0 @@
|
||||
# 邮箱验证码登录流程文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细描述了 YuMi iOS 应用中 `LoginTypesViewController` 在 `LoginDisplayType_email` 模式下的邮箱验证码登录流程。该流程实现了基于邮箱和验证码的用户认证机制。
|
||||
|
||||
## 系统架构
|
||||
|
||||
### 核心组件
|
||||
- **LoginTypesViewController**: 登录类型控制器,负责 UI 展示和用户交互
|
||||
- **LoginPresenter**: 登录业务逻辑处理器,负责与 API 交互
|
||||
- **LoginInputItemView**: 输入组件,提供邮箱和验证码输入界面
|
||||
- **Api+Login**: 登录相关 API 接口封装
|
||||
- **AccountInfoStorage**: 账户信息本地存储管理
|
||||
|
||||
### 数据模型
|
||||
|
||||
#### LoginDisplayType 枚举
|
||||
```objc
|
||||
typedef NS_ENUM(NSUInteger, LoginDisplayType) {
|
||||
LoginDisplayType_id, // ID 登录
|
||||
LoginDisplayType_email, // 邮箱登录 ✓
|
||||
LoginDisplayType_phoneNum, // 手机号登录
|
||||
LoginDisplayType_email_forgetPassword, // 邮箱忘记密码
|
||||
LoginDisplayType_phoneNum_forgetPassword, // 手机号忘记密码
|
||||
};
|
||||
```
|
||||
|
||||
#### LoginInputType 枚举
|
||||
```objc
|
||||
typedef NS_ENUM(NSUInteger, LoginInputType) {
|
||||
LoginInputType_email, // 邮箱输入
|
||||
LoginInputType_verificationCode, // 验证码输入
|
||||
LoginInputType_login, // 登录按钮
|
||||
// ... 其他类型
|
||||
};
|
||||
```
|
||||
|
||||
#### GetSmsType 验证码类型
|
||||
```objc
|
||||
typedef NS_ENUM(NSUInteger, GetSmsType) {
|
||||
GetSmsType_Regist = 1, // 注册(邮箱登录使用此类型)
|
||||
GetSmsType_Login = 2, // 登录
|
||||
GetSmsType_Reset_Password = 3, // 重设密码
|
||||
// ... 其他类型
|
||||
};
|
||||
```
|
||||
|
||||
## 登录流程详解
|
||||
|
||||
### 1. 界面初始化流程
|
||||
|
||||
#### 1.1 控制器初始化
|
||||
```objc
|
||||
// 在 LoginViewController 中点击邮箱登录按钮
|
||||
- (void)didTapEntrcyButton:(UIButton *)sender {
|
||||
if (sender.tag == LoginType_Email) {
|
||||
LoginTypesViewController *vc = [[LoginTypesViewController alloc] init];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
[vc updateLoginType:LoginDisplayType_email]; // 设置为邮箱登录模式
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 输入区域设置
|
||||
```objc
|
||||
- (void)setupEmailInputArea {
|
||||
[self setupInpuArea:LoginInputType_email // 第一行:邮箱输入
|
||||
second:LoginInputType_verificationCode // 第二行:验证码输入
|
||||
third:LoginInputType_none // 第三行:无
|
||||
action:LoginInputType_login // 操作按钮:登录
|
||||
showForgetPassword:NO]; // 不显示忘记密码
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 UI 组件配置
|
||||
- **第一行输入框**: 邮箱地址输入
|
||||
- 占位符: "请输入邮箱地址"
|
||||
- 键盘类型: `UIKeyboardTypeEmailAddress`
|
||||
- 回调: `handleFirstInputContentUpdate`
|
||||
|
||||
- **第二行输入框**: 验证码输入
|
||||
- 占位符: "请输入验证码"
|
||||
- 键盘类型: `UIKeyboardTypeDefault`
|
||||
- 附带"获取验证码"按钮
|
||||
- 回调: `handleSecondInputContentUpdate`
|
||||
|
||||
### 2. 验证码获取流程
|
||||
|
||||
#### 2.1 用户交互触发
|
||||
```objc
|
||||
// 用户点击"获取验证码"按钮
|
||||
[self.secondLineInputView setHandleItemAction:^(LoginInputType inputType) {
|
||||
if (inputType == LoginInputType_verificationCode) {
|
||||
if (self.type == LoginDisplayType_email) {
|
||||
[self handleTapGetMailVerificationCode];
|
||||
}
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
#### 2.2 邮箱验证码获取处理
|
||||
```objc
|
||||
- (void)handleTapGetMailVerificationCode {
|
||||
NSString *email = [self.firstLineInputView inputContent];
|
||||
|
||||
// 邮箱地址验证
|
||||
if (email.length == 0) {
|
||||
[self.secondLineInputView endVerificationCountDown];
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用 Presenter 发送验证码
|
||||
[self.presenter sendMailVerificationCode:email type:GetSmsType_Regist];
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 Presenter 层处理
|
||||
```objc
|
||||
- (void)sendMailVerificationCode:(NSString *)emailAddress type:(NSInteger)type {
|
||||
// DES 加密邮箱地址
|
||||
NSString *desEmail = [DESEncrypt encryptUseDES:emailAddress
|
||||
key:KeyWithType(KeyType_PasswordEncode)];
|
||||
|
||||
@kWeakify(self);
|
||||
[Api emailGetCode:[self createHttpCompletion:^(BaseModel *data) {
|
||||
@kStrongify(self);
|
||||
if ([[self getView] respondsToSelector:@selector(emailCodeSucess:type:)]) {
|
||||
[[self getView] emailCodeSucess:@"" type:type];
|
||||
}
|
||||
} fail:^(NSInteger code, NSString *msg) {
|
||||
@kStrongify(self);
|
||||
if ([[self getView] respondsToSelector:@selector(emailCodeFailure)]) {
|
||||
[[self getView] emailCodeFailure];
|
||||
}
|
||||
} showLoading:YES errorToast:YES]
|
||||
emailAddress:desEmail
|
||||
type:@(type)];
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 API 接口调用
|
||||
```objc
|
||||
+ (void)emailGetCode:(HttpRequestHelperCompletion)completion
|
||||
emailAddress:(NSString *)emailAddress
|
||||
type:(NSNumber *)type {
|
||||
[self makeRequest:@"email/getCode"
|
||||
method:HttpRequestHelperMethodPOST
|
||||
completion:completion, __FUNCTION__, emailAddress, type, nil];
|
||||
}
|
||||
```
|
||||
|
||||
**API 详情**:
|
||||
- **接口路径**: `POST /email/getCode`
|
||||
- **请求参数**:
|
||||
- `emailAddress`: 邮箱地址(DES 加密)
|
||||
- `type`: 验证码类型(1=注册)
|
||||
|
||||
#### 2.5 获取验证码成功处理
|
||||
```objc
|
||||
- (void)emailCodeSucess:(NSString *)message type:(GetSmsType)type {
|
||||
[self showSuccessToast:YMLocalizedString(@"XPLoginPhoneViewController2")]; // "验证码已发送"
|
||||
[self.secondLineInputView startVerificationCountDown]; // 开始倒计时
|
||||
[self.secondLineInputView displayKeyboard]; // 显示键盘
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.6 获取验证码失败处理
|
||||
```objc
|
||||
- (void)emailCodeFailure {
|
||||
[self.secondLineInputView endVerificationCountDown]; // 结束倒计时
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 邮箱登录流程
|
||||
|
||||
#### 3.1 登录按钮状态检查
|
||||
```objc
|
||||
- (void)checkActionButtonStatus {
|
||||
switch (self.type) {
|
||||
case LoginDisplayType_email: {
|
||||
NSString *accountString = [self.firstLineInputView inputContent]; // 邮箱
|
||||
NSString *codeString = [self.secondLineInputView inputContent]; // 验证码
|
||||
|
||||
// 只有当邮箱和验证码都不为空时才启用登录按钮
|
||||
if (![NSString isEmpty:accountString] && ![NSString isEmpty:codeString]) {
|
||||
self.bottomActionButton.enabled = YES;
|
||||
} else {
|
||||
self.bottomActionButton.enabled = NO;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 登录按钮点击处理
|
||||
```objc
|
||||
- (void)didTapActionButton {
|
||||
[self.view endEditing:true];
|
||||
|
||||
switch (self.type) {
|
||||
case LoginDisplayType_email: {
|
||||
// 调用 Presenter 进行邮箱登录
|
||||
[self.presenter loginWithEmail:[self.firstLineInputView inputContent]
|
||||
code:[self.secondLineInputView inputContent]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 Presenter 层登录处理
|
||||
```objc
|
||||
- (void)loginWithEmail:(NSString *)email code:(NSString *)code {
|
||||
// DES 加密邮箱地址
|
||||
NSString *desMail = [DESEncrypt encryptUseDES:email
|
||||
key:KeyWithType(KeyType_PasswordEncode)];
|
||||
|
||||
@kWeakify(self);
|
||||
[Api loginWithCode:[self createHttpCompletion:^(BaseModel *data) {
|
||||
@kStrongify(self);
|
||||
|
||||
// 解析账户模型
|
||||
AccountModel *accountModel = [AccountModel modelWithDictionary:data.data];
|
||||
|
||||
// 保存账户信息
|
||||
if (accountModel && accountModel.access_token.length > 0) {
|
||||
[[AccountInfoStorage instance] saveAccountInfo:accountModel];
|
||||
}
|
||||
|
||||
// 通知登录成功
|
||||
if ([[self getView] respondsToSelector:@selector(loginSuccess)]) {
|
||||
[[self getView] loginSuccess];
|
||||
}
|
||||
} fail:^(NSInteger code, NSString *msg) {
|
||||
@kStrongify(self);
|
||||
[[self getView] loginFailWithMsg:msg];
|
||||
} errorToast:NO]
|
||||
email:desMail
|
||||
code:code
|
||||
client_secret:clinet_s // 客户端密钥
|
||||
version:@"1"
|
||||
client_id:@"erban-client"
|
||||
grant_type:@"email"]; // 邮箱登录类型
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4 API 接口调用
|
||||
```objc
|
||||
+ (void)loginWithCode:(HttpRequestHelperCompletion)completion
|
||||
email:(NSString *)email
|
||||
code:(NSString *)code
|
||||
client_secret:(NSString *)client_secret
|
||||
version:(NSString *)version
|
||||
client_id:(NSString *)client_id
|
||||
grant_type:(NSString *)grant_type {
|
||||
|
||||
NSString *fang = [NSString stringFromBase64String:@"b2F1dGgvdG9rZW4="]; // oauth/token
|
||||
[self makeRequest:fang
|
||||
method:HttpRequestHelperMethodPOST
|
||||
completion:completion, __FUNCTION__, email, code, client_secret,
|
||||
version, client_id, grant_type, nil];
|
||||
}
|
||||
```
|
||||
|
||||
**API 详情**:
|
||||
- **接口路径**: `POST /oauth/token`
|
||||
- **请求参数**:
|
||||
- `email`: 邮箱地址(DES 加密)
|
||||
- `code`: 验证码
|
||||
- `client_secret`: 客户端密钥
|
||||
- `version`: 版本号 "1"
|
||||
- `client_id`: 客户端ID "erban-client"
|
||||
- `grant_type`: 授权类型 "email"
|
||||
|
||||
#### 3.5 登录成功处理
|
||||
```objc
|
||||
- (void)loginSuccess {
|
||||
[self showSuccessToast:YMLocalizedString(@"XPLoginPhoneViewController1")]; // "登录成功"
|
||||
[PILoginManager loginWithVC:self isLoginPhone:NO]; // 执行登录后续处理
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.6 登录失败处理
|
||||
```objc
|
||||
- (void)loginFailWithMsg:(NSString *)msg {
|
||||
[self showSuccessToast:msg]; // 显示错误信息
|
||||
}
|
||||
```
|
||||
|
||||
## 数据流时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant VC as LoginTypesViewController
|
||||
participant IV as LoginInputItemView
|
||||
participant P as LoginPresenter
|
||||
participant API as Api+Login
|
||||
participant Storage as AccountInfoStorage
|
||||
|
||||
Note over User,Storage: 1. 初始化邮箱登录界面
|
||||
User->>VC: 选择邮箱登录
|
||||
VC->>VC: updateLoginType(LoginDisplayType_email)
|
||||
VC->>VC: setupEmailInputArea()
|
||||
VC->>IV: 创建邮箱输入框
|
||||
VC->>IV: 创建验证码输入框
|
||||
|
||||
Note over User,Storage: 2. 获取邮箱验证码
|
||||
User->>IV: 输入邮箱地址
|
||||
User->>IV: 点击"获取验证码"
|
||||
IV->>VC: handleTapGetMailVerificationCode
|
||||
VC->>VC: 验证邮箱地址非空
|
||||
VC->>P: sendMailVerificationCode(email, GetSmsType_Regist)
|
||||
P->>P: DES加密邮箱地址
|
||||
P->>API: emailGetCode(encryptedEmail, type=1)
|
||||
API-->>P: 验证码发送结果
|
||||
P-->>VC: emailCodeSucess / emailCodeFailure
|
||||
VC->>IV: startVerificationCountDown / endVerificationCountDown
|
||||
VC->>User: 显示成功/失败提示
|
||||
|
||||
Note over User,Storage: 3. 邮箱验证码登录
|
||||
User->>IV: 输入验证码
|
||||
IV->>VC: 输入内容变化回调
|
||||
VC->>VC: checkActionButtonStatus()
|
||||
VC->>User: 启用/禁用登录按钮
|
||||
User->>VC: 点击登录按钮
|
||||
VC->>VC: didTapActionButton()
|
||||
VC->>P: loginWithEmail(email, code)
|
||||
P->>P: DES加密邮箱地址
|
||||
P->>API: loginWithCode(email, code, ...)
|
||||
API-->>P: OAuth Token 响应
|
||||
P->>P: 解析 AccountModel
|
||||
P->>Storage: saveAccountInfo(accountModel)
|
||||
P-->>VC: loginSuccess / loginFailWithMsg
|
||||
VC->>User: 显示登录结果
|
||||
VC->>User: 跳转到主界面
|
||||
```
|
||||
|
||||
## 安全机制
|
||||
|
||||
### 1. 数据加密
|
||||
- **邮箱地址加密**: 使用 DES 算法加密邮箱地址后传输
|
||||
```objc
|
||||
NSString *desEmail = [DESEncrypt encryptUseDES:email key:KeyWithType(KeyType_PasswordEncode)];
|
||||
```
|
||||
|
||||
### 2. 输入验证
|
||||
- **邮箱格式验证**: 通过 `UIKeyboardTypeEmailAddress` 键盘类型引导正确输入
|
||||
- **非空验证**: 邮箱和验证码都必须非空才能执行登录
|
||||
|
||||
### 3. 验证码安全
|
||||
- **时效性**: 验证码具有倒计时机制,防止重复获取
|
||||
- **类型标识**: 使用 `GetSmsType_Regist = 1` 标识登录验证码
|
||||
|
||||
### 4. 网络安全
|
||||
- **错误处理**: 完整的成功/失败回调机制
|
||||
- **加载状态**: `showLoading:YES` 防止重复请求
|
||||
- **错误提示**: `errorToast:YES` 显示网络错误
|
||||
|
||||
## 错误处理机制
|
||||
|
||||
### 1. 邮箱验证码获取错误
|
||||
```objc
|
||||
- (void)emailCodeFailure {
|
||||
[self.secondLineInputView endVerificationCountDown]; // 停止倒计时
|
||||
// 用户可以重新获取验证码
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 登录失败处理
|
||||
```objc
|
||||
- (void)loginFailWithMsg:(NSString *)msg {
|
||||
[self showSuccessToast:msg]; // 显示具体错误信息
|
||||
// 用户可以重新尝试登录
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 网络请求错误
|
||||
- **自动重试**: 用户可以手动重新点击获取验证码或登录
|
||||
- **错误提示**: 通过 Toast 显示具体错误信息
|
||||
- **状态恢复**: 失败后恢复按钮可点击状态
|
||||
|
||||
## 本地化支持
|
||||
|
||||
### 关键文本资源
|
||||
- `@"20.20.51_text_1"`: "邮箱登录"
|
||||
- `@"20.20.51_text_4"`: "请输入邮箱地址"
|
||||
- `@"20.20.51_text_7"`: "请输入验证码"
|
||||
- `@"XPLoginPhoneViewController2"`: "验证码已发送"
|
||||
- `@"XPLoginPhoneViewController1"`: "登录成功"
|
||||
|
||||
### 多语言支持
|
||||
- 简体中文 (`zh-Hant.lproj`)
|
||||
- 英文 (`en.lproj`)
|
||||
- 阿拉伯语 (`ar.lproj`)
|
||||
- 土耳其语 (`tr.lproj`)
|
||||
|
||||
## 依赖组件
|
||||
|
||||
### 外部框架
|
||||
- **MASConstraintMaker**: 自动布局
|
||||
- **ReactiveObjC**: 响应式编程(部分组件使用)
|
||||
|
||||
### 内部组件
|
||||
- **YMLocalizedString**: 本地化字符串管理
|
||||
- **DESEncrypt**: DES 加密工具
|
||||
- **AccountInfoStorage**: 账户信息存储
|
||||
- **HttpRequestHelper**: 网络请求管理
|
||||
|
||||
## 扩展和维护
|
||||
|
||||
### 新增功能建议
|
||||
1. **邮箱格式验证**: 添加正则表达式验证邮箱格式
|
||||
2. **验证码长度限制**: 限制验证码输入长度
|
||||
3. **自动填充**: 支持系统邮箱自动填充
|
||||
4. **记住邮箱**: 保存最近使用的邮箱地址
|
||||
|
||||
### 性能优化
|
||||
1. **请求去重**: 防止短时间内重复请求验证码
|
||||
2. **缓存机制**: 缓存验证码倒计时状态
|
||||
3. **网络优化**: 添加请求超时和重试机制
|
||||
|
||||
### 代码维护
|
||||
1. **常量管理**: 将硬编码字符串提取为常量
|
||||
2. **错误码统一**: 统一管理API错误码
|
||||
3. **日志记录**: 添加详细的操作日志
|
||||
|
||||
## 总结
|
||||
|
||||
邮箱验证码登录流程是一个完整的用户认证系统,包含了界面展示、验证码获取、用户登录、数据存储等完整环节。该流程具有良好的安全性、用户体验和错误处理机制,符合现代移动应用的认证标准。
|
||||
|
||||
通过本文档,开发人员可以全面了解邮箱登录的实现细节,便于后续的功能扩展和维护工作。
|
@@ -1,262 +0,0 @@
|
||||
# OAuth/Ticket 认证系统 API 文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了 YuMi 应用中 OAuth 认证和 Ticket 会话管理的完整流程。系统采用两阶段认证机制:
|
||||
1. **OAuth 阶段**:用户登录获取 `access_token`
|
||||
2. **Ticket 阶段**:使用 `access_token` 获取业务会话 `ticket`
|
||||
|
||||
## 认证流程架构
|
||||
|
||||
### 核心组件
|
||||
- **AccountInfoStorage**: 负责账户信息和 ticket 的本地存储
|
||||
- **HttpRequestHelper**: 网络请求管理,自动添加认证头
|
||||
- **Api+Login**: 登录相关 API 接口
|
||||
- **Api+Main**: Ticket 获取相关 API 接口
|
||||
|
||||
### 认证数据模型
|
||||
|
||||
#### AccountModel
|
||||
```objc
|
||||
@interface AccountModel : PIBaseModel
|
||||
@property (nonatomic, assign) NSString *uid; // 用户唯一标识
|
||||
@property (nonatomic, copy) NSString *jti; // JWT ID
|
||||
@property (nonatomic, copy) NSString *token_type; // Token 类型
|
||||
@property (nonatomic, copy) NSString *refresh_token; // 刷新令牌
|
||||
@property (nonatomic, copy) NSString *netEaseToken; // 网易云信令牌
|
||||
@property (nonatomic, copy) NSString *access_token; // OAuth 访问令牌
|
||||
@property (nonatomic, assign) NSNumber *expires_in; // 过期时间
|
||||
@end
|
||||
```
|
||||
|
||||
## API 接口详情
|
||||
|
||||
### 1. OAuth 登录接口
|
||||
|
||||
#### 1.1 手机验证码登录
|
||||
```objc
|
||||
+ (void)loginWithCode:(HttpRequestHelperCompletion)completion
|
||||
phone:(NSString *)phone
|
||||
code:(NSString *)code
|
||||
client_secret:(NSString *)client_secret
|
||||
version:(NSString *)version
|
||||
client_id:(NSString *)client_id
|
||||
grant_type:(NSString *)grant_type
|
||||
phoneAreaCode:(NSString *)phoneAreaCode;
|
||||
```
|
||||
|
||||
**接口路径**: `POST /oauth/token`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| phone | String | 是 | 手机号(DES加密) |
|
||||
| code | String | 是 | 验证码 |
|
||||
| client_secret | String | 是 | 客户端密钥,固定值:"uyzjdhds" |
|
||||
| version | String | 是 | 版本号,固定值:"1" |
|
||||
| client_id | String | 是 | 客户端ID,固定值:"erban-client" |
|
||||
| grant_type | String | 是 | 授权类型,验证码登录为:"sms_code" |
|
||||
| phoneAreaCode | String | 是 | 手机区号 |
|
||||
|
||||
**返回数据**: AccountModel 对象
|
||||
|
||||
#### 1.2 手机密码登录
|
||||
```objc
|
||||
+ (void)loginWithPassword:(HttpRequestHelperCompletion)completion
|
||||
phone:(NSString *)phone
|
||||
password:(NSString *)password
|
||||
client_secret:(NSString *)client_secret
|
||||
version:(NSString *)version
|
||||
client_id:(NSString *)client_id
|
||||
grant_type:(NSString *)grant_type;
|
||||
```
|
||||
|
||||
**接口路径**: `POST /oauth/token`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| phone | String | 是 | 手机号(DES加密) |
|
||||
| password | String | 是 | 密码(DES加密) |
|
||||
| client_secret | String | 是 | 客户端密钥 |
|
||||
| version | String | 是 | 版本号 |
|
||||
| client_id | String | 是 | 客户端ID |
|
||||
| grant_type | String | 是 | 授权类型,密码登录为:"password" |
|
||||
|
||||
#### 1.3 第三方登录
|
||||
```objc
|
||||
+ (void)loginWithThirdPart:(HttpRequestHelperCompletion)completion
|
||||
openid:(NSString *)openid
|
||||
unionid:(NSString *)unionid
|
||||
access_token:(NSString *)access_token
|
||||
type:(NSString *)type;
|
||||
```
|
||||
|
||||
**接口路径**: `POST /acc/third/login`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| openid | String | 是 | 第三方平台用户唯一标识 |
|
||||
| unionid | String | 是 | 第三方平台联合ID |
|
||||
| access_token | String | 是 | 第三方平台访问令牌 |
|
||||
| type | String | 是 | 第三方平台类型(1:Apple, 2:Facebook, 3:Google等) |
|
||||
|
||||
### 2. Ticket 获取接口
|
||||
|
||||
#### 2.1 获取 Ticket
|
||||
```objc
|
||||
+ (void)requestTicket:(HttpRequestHelperCompletion)completion
|
||||
access_token:(NSString *)accessToken
|
||||
issue_type:(NSString *)issueType;
|
||||
```
|
||||
|
||||
**接口路径**: `POST /oauth/ticket`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| access_token | String | 是 | OAuth 登录获取的访问令牌 |
|
||||
| issue_type | String | 是 | 签发类型,固定值:"multi" |
|
||||
|
||||
**返回数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"tickets": [
|
||||
{
|
||||
"ticket": "eyJhbGciOiJIUzI1NiJ9..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. HTTP 请求头配置
|
||||
|
||||
所有业务 API 请求都会自动添加以下请求头:
|
||||
|
||||
```objc
|
||||
// 在 HttpRequestHelper 中自动配置
|
||||
- (void)setupHeader {
|
||||
AFHTTPSessionManager *client = [HttpRequestHelper requestManager];
|
||||
|
||||
// 用户ID头
|
||||
if ([[AccountInfoStorage instance] getUid].length > 0) {
|
||||
[client.requestSerializer setValue:[[AccountInfoStorage instance] getUid]
|
||||
forHTTPHeaderField:@"pub_uid"];
|
||||
}
|
||||
|
||||
// Ticket 认证头
|
||||
if ([[AccountInfoStorage instance] getTicket].length > 0) {
|
||||
[client.requestSerializer setValue:[[AccountInfoStorage instance] getTicket]
|
||||
forHTTPHeaderField:@"pub_ticket"];
|
||||
}
|
||||
|
||||
// 其他公共头
|
||||
[client.requestSerializer setValue:[NSBundle uploadLanguageText]
|
||||
forHTTPHeaderField:@"Accept-Language"];
|
||||
[client.requestSerializer setValue:PI_App_Version
|
||||
forHTTPHeaderField:@"App-Version"];
|
||||
}
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 完整登录流程示例
|
||||
|
||||
```objc
|
||||
// 1. 用户登录获取 access_token
|
||||
[Api loginWithCode:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200) {
|
||||
// 保存账户信息
|
||||
AccountModel *accountModel = [AccountModel modelWithDictionary:data.data];
|
||||
[[AccountInfoStorage instance] saveAccountInfo:accountModel];
|
||||
|
||||
// 2. 使用 access_token 获取 ticket
|
||||
[Api requestTicket:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200) {
|
||||
NSArray *tickets = [data.data valueForKey:@"tickets"];
|
||||
NSString *ticket = [tickets[0] valueForKey:@"ticket"];
|
||||
|
||||
// 保存 ticket
|
||||
[[AccountInfoStorage instance] saveTicket:ticket];
|
||||
|
||||
// 3. 登录成功,可以进行业务操作
|
||||
[self navigateToMainPage];
|
||||
}
|
||||
} access_token:accountModel.access_token issue_type:@"multi"];
|
||||
}
|
||||
} phone:encryptedPhone
|
||||
code:verificationCode
|
||||
client_secret:@"uyzjdhds"
|
||||
version:@"1"
|
||||
client_id:@"erban-client"
|
||||
grant_type:@"sms_code"
|
||||
phoneAreaCode:areaCode];
|
||||
```
|
||||
|
||||
### 自动登录流程
|
||||
|
||||
```objc
|
||||
- (void)autoLogin {
|
||||
// 检查本地是否有账户信息
|
||||
AccountModel *accountModel = [[AccountInfoStorage instance] getCurrentAccountInfo];
|
||||
if (accountModel == nil || accountModel.access_token == nil) {
|
||||
[self tokenInvalid]; // 跳转到登录页
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有有效的 ticket
|
||||
if ([[AccountInfoStorage instance] getTicket].length > 0) {
|
||||
[[self getView] autoLoginSuccess];
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用 access_token 重新获取 ticket
|
||||
[Api requestTicket:^(BaseModel * _Nonnull data) {
|
||||
NSArray *tickets = [data.data valueForKey:@"tickets"];
|
||||
NSString *ticket = [tickets[0] valueForKey:@"ticket"];
|
||||
[[AccountInfoStorage instance] saveTicket:ticket];
|
||||
[[self getView] autoLoginSuccess];
|
||||
} fail:^(NSInteger code, NSString * _Nullable msg) {
|
||||
[self logout]; // ticket 获取失败,重新登录
|
||||
} access_token:accountModel.access_token issue_type:@"multi"];
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 401 未授权错误
|
||||
当接收到 401 状态码时,系统会自动处理:
|
||||
|
||||
```objc
|
||||
// 在 HttpRequestHelper 中
|
||||
if (response && response.statusCode == 401) {
|
||||
failure(response.statusCode, YMLocalizedString(@"HttpRequestHelper7"));
|
||||
// 通常需要重新登录
|
||||
}
|
||||
```
|
||||
|
||||
### Ticket 过期处理
|
||||
- Ticket 过期时服务器返回 401 错误
|
||||
- 客户端应该使用保存的 `access_token` 重新获取 ticket
|
||||
- 如果 `access_token` 也过期,则需要用户重新登录
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
1. **数据加密**: 敏感信息(手机号、密码)使用 DES 加密传输
|
||||
2. **本地存储**:
|
||||
- `access_token` 存储在文件系统中
|
||||
- `ticket` 存储在内存中,应用重启需重新获取
|
||||
3. **请求头**: 所有业务请求自动携带 `pub_uid` 和 `pub_ticket` 头
|
||||
4. **错误处理**: 建立完善的 401 错误重试机制
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `YuMi/Structure/MVP/Model/AccountInfoStorage.h/m` - 账户信息存储管理
|
||||
- `YuMi/Modules/YMLogin/Api/Api+Login.h/m` - 登录相关接口
|
||||
- `YuMi/Modules/YMTabbar/Api/Api+Main.h/m` - Ticket 获取接口
|
||||
- `YuMi/Network/HttpRequestHelper.h/m` - 网络请求管理
|
||||
- `YuMi/Structure/MVP/Model/AccountModel.h/m` - 账户数据模型
|
13
Podfile
@@ -6,9 +6,12 @@ target 'YuMi' do
|
||||
#pag动画
|
||||
pod 'libpag'
|
||||
pod 'Bugly'
|
||||
pod 'Adjust'
|
||||
pod 'Firebase/Analytics'
|
||||
pod 'FBSDKLoginKit'
|
||||
pod 'FBSDKCoreKit'
|
||||
pod 'FBSDKShareKit'
|
||||
pod 'LineSDKSwift'
|
||||
# 滑动标签栏
|
||||
pod 'JXCategoryView'
|
||||
pod 'JXPagingView/Pager'
|
||||
@@ -16,7 +19,6 @@ target 'YuMi' do
|
||||
pod 'MJExtension'
|
||||
#图片加载
|
||||
pod 'SDWebImage'
|
||||
# pod 'SDWebImageWebPCoder' 用于加载 webP
|
||||
pod 'FLAnimatedImage'
|
||||
pod 'SDWebImageFLPlugin' # 对FLAnimatedImage和SDWebImage的桥接
|
||||
pod 'AFNetworking'
|
||||
@@ -44,7 +46,7 @@ target 'YuMi' do
|
||||
#上传音乐
|
||||
pod 'CocoaAsyncSocket',:modular_headers => true
|
||||
#声网
|
||||
|
||||
pod 'AgoraRtcEngine_iOS'
|
||||
pod 'SSKeychain'
|
||||
pod 'Base64'
|
||||
#pop动画
|
||||
@@ -57,15 +59,18 @@ target 'YuMi' do
|
||||
pod 'mob_linksdk_pro'
|
||||
pod 'mob_sharesdk'
|
||||
pod 'mob_sharesdk/ShareSDKPlatforms/Apple'
|
||||
#pod 'mob_sharesdk/ShareSDKPlatforms/Line'
|
||||
pod 'mob_sharesdk/ShareSDKExtension'
|
||||
|
||||
pod 'UMCommon'
|
||||
pod 'UMDevice'
|
||||
pod 'lottie-ios'
|
||||
pod 'WeexSDK'
|
||||
pod 'ZLCollectionViewFlowLayout'
|
||||
pod 'WMZDropDownMenu'
|
||||
pod 'TABAnimated'
|
||||
pod 'YuMi',:path=>'yum'
|
||||
pod 'QCloudCOSXML'
|
||||
pod 'TYCyclePagerView'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
@@ -73,8 +78,6 @@ post_install do |installer|
|
||||
project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
|
||||
config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym'
|
||||
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
||||
xcconfig_path = config.base_configuration_reference.real_path
|
||||
xcconfig = File.read(xcconfig_path)
|
||||
xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
|
||||
|
@@ -1,86 +0,0 @@
|
||||
# 公共房间消息转发功能实现
|
||||
|
||||
## 功能概述
|
||||
|
||||
实现了从 PublicRoomManager 转发特定消息到房间中的功能。当 PublicRoomManager 接收到 attachment.first 为 106 的消息时,会自动转发到当前活跃的房间中。
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. 通知机制
|
||||
- 使用 NSNotificationCenter 进行消息转发
|
||||
- 通知名称:`@"MessageFromPublicRoomWithAttachmentNotification"`
|
||||
- 通知对象:NIMMessage 对象
|
||||
|
||||
### 2. 修改的文件
|
||||
|
||||
#### PublicRoomManager.m
|
||||
- 在 `onRecvMessages:` 方法中添加转发逻辑
|
||||
- 当检测到 `attachment.first == 106` 时发送通知
|
||||
|
||||
#### XPRoomViewController.m
|
||||
- 在 `setupNotifications` 方法中注册通知监听
|
||||
- 添加 `handlePublicRoomMessageForward:` 方法处理转发的消息
|
||||
- 在 `dealloc` 中自动移除通知监听
|
||||
|
||||
#### YUMIConstant.m
|
||||
- 添加常量定义:`kMessageFromPublicRoomWithAttachmentNotification`(已添加但当前使用字符串字面量)
|
||||
|
||||
#### XPRoomViewController.h
|
||||
- 添加常量声明(已添加但当前使用字符串字面量)
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. **消息接收**:PublicRoomManager 接收到公共房间消息
|
||||
2. **类型检查**:检查 attachment.first 是否为 106
|
||||
3. **发送通知**:如果是 106 类型,发送转发通知
|
||||
4. **接收处理**:XPRoomViewController 接收通知并处理
|
||||
5. **消息显示**:通过现有的消息处理流程显示在房间中
|
||||
|
||||
## 代码示例
|
||||
|
||||
### 发送通知(PublicRoomManager.m)
|
||||
```objective-c
|
||||
if (attachment && attachment.first == 106) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"MessageFromPublicRoomWithAttachmentNotification"
|
||||
object:message];
|
||||
NSLog(@"PublicRoomManager: 转发106类型消息到房间");
|
||||
}
|
||||
```
|
||||
|
||||
### 接收处理(XPRoomViewController.m)
|
||||
```objective-c
|
||||
- (void)handlePublicRoomMessageForward:(NSNotification *)notification {
|
||||
NIMMessage *message = notification.object;
|
||||
if (![message isKindOfClass:[NIMMessage class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查房间是否处于活跃状态
|
||||
if (!self.roomInfo || !self.messageContainerView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用现有的消息处理流程
|
||||
[self.messageContainerView handleNIMCustomMessage:message];
|
||||
}
|
||||
```
|
||||
|
||||
## 测试场景
|
||||
|
||||
1. **正常转发**:公共房间收到106类型消息时正确转发
|
||||
2. **房间状态**:房间最小化、关闭等状态下的处理
|
||||
3. **消息过滤**:确保转发的消息经过正确的过滤流程
|
||||
4. **性能影响**:确保不影响现有消息处理性能
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 消息会经过现有的 `isCanDisplayMessage` 过滤
|
||||
2. 支持最小化房间的特殊处理
|
||||
3. 自动处理内存管理(在 dealloc 中移除监听)
|
||||
4. 包含完整的错误检查和日志记录
|
||||
|
||||
## 扩展性
|
||||
|
||||
如果将来需要转发其他类型的消息,可以:
|
||||
1. 修改条件判断(如 `attachment.first == 107`)
|
||||
2. 或者使用更通用的通知名称,在通知数据中携带消息类型信息
|
@@ -50,13 +50,6 @@
|
||||
ReferencedContainer = "container:YuMi.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "OS_ACTIVITY_MODE"
|
||||
value = "disable"
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@@ -24,38 +24,46 @@
|
||||
#import "XPRoomViewController.h"
|
||||
#import "XCCurrentVCStackManager.h"
|
||||
#import "ClientConfig.h"
|
||||
#import <Adjust/Adjust.h>
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
#import <Bugly/Bugly.h>
|
||||
#import <UIKit/UIDevice.h>
|
||||
|
||||
#import "YuMi-swift.h"
|
||||
|
||||
|
||||
UIKIT_EXTERN NSString * kYouMiNumberCountKey;
|
||||
UIKIT_EXTERN NSString * adImageName;
|
||||
|
||||
|
||||
|
||||
@implementation AppDelegate (ThirdConfig)
|
||||
|
||||
|
||||
/// 初始化一些第三方配置
|
||||
- (void)initThirdConfig{
|
||||
[self setLanguage];
|
||||
- (void)initThirdConfig {
|
||||
[self configShareSDK];
|
||||
[self configNIMSDK];
|
||||
[self initEmojiData];
|
||||
[self configAdjust];
|
||||
[self configBugly];
|
||||
[self registerNot];
|
||||
[self initEmojiData];
|
||||
[self setLanguage];
|
||||
}
|
||||
|
||||
-(void)setLanguage{
|
||||
UISemanticContentAttribute attribute = UISemanticContentAttributeForceLeftToRight;
|
||||
|
||||
if (isMSRTL()) {
|
||||
attribute = UISemanticContentAttributeForceRightToLeft;
|
||||
[UIView appearance].semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
|
||||
[UISearchBar appearance].semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
|
||||
|
||||
|
||||
} else {
|
||||
[UIView appearance].semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
|
||||
[UISearchBar appearance].semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
|
||||
|
||||
|
||||
}
|
||||
|
||||
[UIView appearance].semanticContentAttribute = attribute;
|
||||
[UISearchBar appearance].semanticContentAttribute = attribute;
|
||||
}
|
||||
|
||||
-(void)registerNot{
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
@@ -71,39 +79,36 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
崩溃收集 Bugly
|
||||
*/
|
||||
|
||||
- (void) configBugly {
|
||||
|
||||
BuglyConfig *config = [[BuglyConfig alloc] init];
|
||||
|
||||
config.blockMonitorTimeout = 5;
|
||||
config.unexpectedTerminatingDetectionEnable = YES; // 非正常退出事件记录开关,默认关闭
|
||||
|
||||
#ifdef DEBUG
|
||||
config.debugMode = NO;//YES; // debug 模式下,开启调试模式
|
||||
config.channel = [YYUtility getAppSource];
|
||||
config.reportLogLevel = BuglyLogLevelWarn;// BuglyLogLevelSilent; // BuglyLogLevelVerbose; // 设置打印日志级别
|
||||
[Bugly startWithAppId:@"c937fd00f7" config:config];
|
||||
config.debugMode = YES; // debug 模式下,开启调试模式
|
||||
config.channel = [YYUtility getAppSource];; config.blockMonitorEnable = YES; // 卡顿监控开关,默认关闭
|
||||
config.reportLogLevel = BuglyLogLevelVerbose; // 设置打印日志级别
|
||||
[Bugly startWithAppId:@"86cf73b8d5" config:config];
|
||||
#else
|
||||
config.unexpectedTerminatingDetectionEnable = YES; // 非正常退出事件记录开关,默认关闭
|
||||
config.debugMode = NO; // release 模式下,关闭调试模式
|
||||
config.channel = [YYUtility getAppSource];;
|
||||
config.blockMonitorEnable = YES; // 卡顿监控开关,默认关闭
|
||||
config.channel = [YYUtility getAppSource];; config.blockMonitorEnable = NO; // 卡顿监控开关,默认关闭
|
||||
config.reportLogLevel = BuglyLogLevelWarn; // 设置自定义日志上报的级别,默认不上报自定义日志
|
||||
NSString *buylyKey = @"8627948559";
|
||||
NSString *buylyKey = isEnterprise == NO ? @"5334684d86" : @"d65df59a68";
|
||||
[Bugly startWithAppId:buylyKey config:config];
|
||||
#endif
|
||||
|
||||
}
|
||||
- (void)configNIMSDK {
|
||||
//推荐在程序启动的时候初始化 NIMSDK
|
||||
NSString *appKey = [[ClientConfig shareConfig].configInfo nimKey];
|
||||
if ([NSString isEmpty:appKey]) {
|
||||
appKey = KeyWithType(KeyType_NetEase);
|
||||
}
|
||||
|
||||
NSString *appKey = KeyWithType(KeyType_NetEase);
|
||||
NIMSDKOption *option = [NIMSDKOption optionWithAppKey:appKey];
|
||||
#ifdef DEBUG
|
||||
option.apnsCername = @"pikoDevelopPush";
|
||||
@@ -111,6 +116,9 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
option.apnsCername = @"newPiko";
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
[[NIMSDK sharedSDK] registerWithOption:option];
|
||||
|
||||
// NIM SDK初始化
|
||||
@@ -124,19 +132,39 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
#endif
|
||||
}
|
||||
|
||||
-(void)configAdjust{
|
||||
NSString *appToken = @"p5sndfu9udq8";
|
||||
NSString *environment;
|
||||
#ifdef DEBUG
|
||||
environment = ADJEnvironmentSandbox;
|
||||
#else
|
||||
environment = ADJEnvironmentProduction;
|
||||
#endif
|
||||
|
||||
ADJConfig*adjustConfig = [ADJConfig configWithAppToken:appToken
|
||||
environment:environment];
|
||||
|
||||
#ifdef DEBUG
|
||||
[adjustConfig setLogLevel:ADJLogLevelVerbose];
|
||||
#else
|
||||
|
||||
#endif
|
||||
[Adjust appDidLaunch:adjustConfig];
|
||||
}
|
||||
- (void)configShareSDK {
|
||||
|
||||
// [PILineLoginManager registerLine];
|
||||
[PILineLoginManager registerLine];
|
||||
|
||||
[ShareSDK registPlatforms:^(SSDKRegister *platformsRegister) {
|
||||
///faceBook
|
||||
// [platformsRegister setupFacebookWithAppkey:@"1266232494209868" appSecret:@"c9b170b383f8be9cdf118823b8632821" displayName:YMLocalizedString(@"AppDelegate_ThirdConfig0")];
|
||||
[platformsRegister setupLineAuthType:SSDKAuthorizeTypeBoth];
|
||||
}];
|
||||
|
||||
NSString *isUpload = [[NSUserDefaults standardUserDefaults]valueForKey:@"kMobLinkUploadPrivacy"];
|
||||
if (isUpload == nil){
|
||||
[MobSDK uploadPrivacyPermissionStatus:YES onResult:nil];
|
||||
[MobSDK uploadPrivacyPermissionStatus:YES onResult:^(BOOL success) {
|
||||
|
||||
}];
|
||||
[[NSUserDefaults standardUserDefaults] setValue:@"YES" forKey:@"kMobLinkUploadPrivacy"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
@@ -144,30 +172,22 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
|
||||
#pragma mark - 表情
|
||||
- (void)initEmojiData {
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
NSArray * dicArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"emoji" ofType:@"plist"]];
|
||||
NSDictionary * dic = [dicArray firstObject];
|
||||
NSArray * emojiArray = dic[@"data"];
|
||||
NSMutableArray * array = [NSMutableArray array];
|
||||
for (int i = 0; i < emojiArray.count; i++) {
|
||||
|
||||
NSDictionary * dic = [emojiArray objectAtIndex:i];
|
||||
UIImage * image = [UIImage imageNamed:dic[@"file"]];
|
||||
QEmotion * info = [[QEmotion alloc] init];
|
||||
|
||||
NSDictionary * dic = [emojiArray xpSafeObjectAtIndex:i];
|
||||
if (dic) {
|
||||
info.displayName = dic[@"tag"];
|
||||
info.identifier = dic[@"id"];
|
||||
}
|
||||
|
||||
info.image = image;
|
||||
|
||||
info.displayName = dic[@"tag"];
|
||||
[array addObject:info];
|
||||
}
|
||||
//在这里强烈建议先预加载一下表情
|
||||
QEmotionHelper *faceManager = [QEmotionHelper sharedEmotionHelper];
|
||||
faceManager.emotionArray = array;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - 广告
|
||||
@@ -178,17 +198,23 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
- (void)setupLaunchADView {
|
||||
NSUserDefaults * kUserDefaults = NSUserDefaults.standardUserDefaults;
|
||||
// 判断沙盒中是否存在广告图片,如果存在,直接显示
|
||||
NSString *adName = [kUserDefaults stringForKey:adImageName];
|
||||
NSString *filePath = [XPAdImageTool.shareImageTool getFilePathWithImageName:adName];
|
||||
NSString *filePath = [XPAdImageTool.shareImageTool getFilePathWithImageName:[kUserDefaults valueForKey:adImageName]];
|
||||
BOOL isExist = [XPAdImageTool.shareImageTool isFileExistWithFilePath:filePath];
|
||||
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
if ([userDefault integerForKey:@"adShow"]) {
|
||||
[userDefault setInteger:[userDefault integerForKey:@"adShow"]+1 forKey:@"adShow"];
|
||||
} else {
|
||||
[userDefault setInteger:1 forKey:@"adShow"];
|
||||
}
|
||||
|
||||
if (isExist) {// 图片存在
|
||||
// if ([kUserDefaults integerForKey:@"adShow"] > 4) {
|
||||
|
||||
if ([userDefault integerForKey:@"adShow"] > 4) {
|
||||
@kWeakify(self);
|
||||
AdvertiseModel *info = [XPAdImageTool.shareImageTool getAdInfoFromCacheInMainWith:adName];
|
||||
NSString *imageName = [kUserDefaults valueForKey:adImageName];
|
||||
AdvertiseModel *info = [XPAdImageTool.shareImageTool getAdInfoFromCacheInMainWith:imageName];
|
||||
XPAdvertiseView *advertiseView = [[XPAdvertiseView alloc] initWithFrame:self.window.bounds];
|
||||
advertiseView.type = info.type;
|
||||
advertiseView.fileModel = info.fillVo;
|
||||
advertiseView.filePath = filePath;
|
||||
advertiseView.dismissHandler = ^(BOOL shouldJump) {
|
||||
@kStrongify(self)
|
||||
@@ -196,11 +222,12 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
return;
|
||||
}
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self advertiseJumpHandleWithInfo:info];
|
||||
[self performSelectorOnMainThread:@selector(advertiseJumpHandleWithInfo:) withObject:info waitUntilDone:NO];
|
||||
});
|
||||
|
||||
};
|
||||
[advertiseView show];
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,13 +250,10 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SplashInfoSkipTypeWeb:
|
||||
case SplashInfoSkipTypeWeb_CP:
|
||||
case SplashInfoSkipTypeWeb_Custom:
|
||||
case SplashInfoSkipTypeWeb_WeekStar: {
|
||||
case SplashInfoSkipTypeWeb: {
|
||||
// 跳转 H5
|
||||
if (info.link.length > 0) {
|
||||
XPWebViewController *webView = [[XPWebViewController alloc] initWithRoomUID:nil];
|
||||
XPWebViewController *webView = [[XPWebViewController alloc]init];
|
||||
webView.url = info.link;
|
||||
[[[XCCurrentVCStackManager shareManager]currentNavigationController] pushViewController:webView animated:YES];
|
||||
}
|
||||
|
@@ -5,27 +5,26 @@
|
||||
// Created by admin on 2023/3/9.
|
||||
//
|
||||
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import <UMCommon/UMCommon.h>
|
||||
#import <MobLinkPro/MobLink.h>
|
||||
#import <MobLinkPro/MLSDKScene.h>
|
||||
#import "AppDelegate.h"
|
||||
#import "TabbarViewController.h"
|
||||
#import "BaseNavigationController.h"
|
||||
#import "AppDelegate+ThirdConfig.h"
|
||||
#import <NIMSDK/NIMSDK.h>
|
||||
#import <UMCommon/UMCommon.h>
|
||||
#import <FBSDKCoreKit/FBSDKCoreKit.h>
|
||||
#import <AppTrackingTransparency/AppTrackingTransparency.h>
|
||||
#import "ClientConfig.h"
|
||||
#import <GoogleSignIn/GoogleSignIn.h>
|
||||
#import "GULAppDelegateSwizzler.h"
|
||||
#import <GoogleSignIn/GoogleSignIn.h>
|
||||
#import "LoginViewController.h"
|
||||
#import "XPLoginViewController.h"
|
||||
#import "AccountModel.h"
|
||||
#import "YuMi-swift.h"
|
||||
#import "SessionViewController.h"
|
||||
#import "LoginFullInfoViewController.h"
|
||||
#import "UIView+VAP.h"
|
||||
#import "SocialShareManager.h"
|
||||
|
||||
@import Firebase;
|
||||
UIKIT_EXTERN NSString * const kOpenRoomNotification;
|
||||
|
||||
@interface AppDelegate ()<IMLSDKRestoreDelegate>
|
||||
@@ -34,130 +33,78 @@ UIKIT_EXTERN NSString * const kOpenRoomNotification;
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
//日志接口
|
||||
void qg_VAP_Logger_handler(VAPLogLevel level, const char* file, int line, const char* func, NSString *module, NSString *format, ...) {
|
||||
|
||||
// 屏蔽 MP4 播放 log
|
||||
return;
|
||||
|
||||
// if (format.UTF8String == nil) {
|
||||
// NSLog(@"log包含非utf-8字符");
|
||||
// return;
|
||||
// }
|
||||
// if (level > VAPLogLevelDebug) {
|
||||
// va_list argList;
|
||||
// va_start(argList, format);
|
||||
// NSString* message = [[NSString alloc] initWithFormat:format arguments:argList];
|
||||
// file = [NSString stringWithUTF8String:file].lastPathComponent.UTF8String;
|
||||
// NSLog(@"<%@> %s(%@):%s [%@] - %@",@(level), file, @(line), func, module, message);
|
||||
// va_end(argList);
|
||||
// }
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
|
||||
UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil];
|
||||
UIViewController *launchScreenVC = [launchStoryboard instantiateInitialViewController];
|
||||
self.window.rootViewController = launchScreenVC;
|
||||
AccountModel *accountModel = [[AccountInfoStorage instance] getCurrentAccountInfo];
|
||||
if (accountModel == nil || accountModel.uid == nil || accountModel.access_token == nil) {
|
||||
XPLoginViewController *lvc = [[XPLoginViewController alloc] init];
|
||||
BaseNavigationController * nav = [[BaseNavigationController alloc] initWithRootViewController:lvc];
|
||||
nav.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
self.window.rootViewController = nav;
|
||||
}else{
|
||||
TabbarViewController *vc = [[TabbarViewController alloc] init];
|
||||
BaseNavigationController *bnc = [[BaseNavigationController alloc] initWithRootViewController:vc];
|
||||
self.window.rootViewController = bnc;
|
||||
}
|
||||
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
[VAPView registerHWDLog:qg_VAP_Logger_handler];
|
||||
|
||||
///设置广告页
|
||||
[self setupLaunchADView];
|
||||
///初始化一些 sdk配置
|
||||
[self initThirdConfig];
|
||||
[self initUM:application launchOptions:launchOptions];
|
||||
|
||||
@kWeakify(self);
|
||||
[[ClientConfig shareConfig] clientConfig:^{
|
||||
@kStrongify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self loadMainPage];
|
||||
[self setupLaunchADView];
|
||||
});
|
||||
}];
|
||||
|
||||
if (@available(iOS 15, *)) {
|
||||
[[UITableView appearance] setSectionHeaderTopPadding:0];
|
||||
// 只有同意过了隐私协议 才初始化
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"kYouMinumbernnagna"]) {
|
||||
///初始化友盟
|
||||
[UMConfigure initWithAppkey:@"6434c6dfd64e686139618269" channel:@"appstore"];
|
||||
}
|
||||
[[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
[FIRApp configure];
|
||||
[MobLink setDelegate:self];
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)initUM:(UIApplication *)application
|
||||
launchOptions:(NSDictionary *)launchOptions {
|
||||
// 只有同意过了隐私协议 才初始化
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"kYouMinumbernnagna"]) {
|
||||
///初始化友盟
|
||||
[UMConfigure initWithAppkey:@"6434c6dfd64e686139618269"
|
||||
channel:@"appstore"];
|
||||
}
|
||||
[MobLink setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)loadMainPage {
|
||||
AccountModel *accountModel = [[AccountInfoStorage instance] getCurrentAccountInfo];
|
||||
if (accountModel == nil ||
|
||||
accountModel.uid == nil ||
|
||||
accountModel.access_token == nil) {
|
||||
[self toLoginPage];
|
||||
}else{
|
||||
[self toHomeTabbarPage];
|
||||
}
|
||||
|
||||
[[ClientConfig shareConfig] clientInit];
|
||||
}
|
||||
|
||||
- (void)toLoginPage {
|
||||
LoginViewController *lvc = [[LoginViewController alloc] init];
|
||||
BaseNavigationController * navigationController = [[BaseNavigationController alloc] initWithRootViewController:lvc];
|
||||
navigationController.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
self.window.rootViewController = navigationController;
|
||||
}
|
||||
|
||||
- (void)toHomeTabbarPage {
|
||||
TabbarViewController *vc = [[TabbarViewController alloc] init];
|
||||
BaseNavigationController *navigationController = [[BaseNavigationController alloc] initWithRootViewController:vc];
|
||||
self.window.rootViewController = navigationController;
|
||||
}
|
||||
|
||||
- (void)IMLSDKWillRestoreScene:(MLSDKScene *)scene
|
||||
Restore:(void (^)(BOOL, RestoreStyle))restoreHandler {
|
||||
- (void) IMLSDKWillRestoreScene:(MLSDKScene *)scene Restore:(void (^)(BOOL, RestoreStyle))restoreHandler
|
||||
{
|
||||
NSString *inviteCode = scene.params[@"inviteCode"];
|
||||
if (inviteCode != nil && [[AccountInfoStorage instance]getUid].length == 0){
|
||||
ClientConfig *config = [ClientConfig shareConfig];
|
||||
config.inviteCode = inviteCode;
|
||||
}
|
||||
|
||||
restoreHandler(YES, MLDefault);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
NSInteger count = [NIMSDK sharedSDK].conversationManager.allUnreadCount;
|
||||
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:count];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
[self getAdvertisingTrackingAuthority];
|
||||
[[NSNotificationCenter defaultCenter]postNotificationName:@"kAppDidBecomeActive" object:nil];
|
||||
}
|
||||
|
||||
- (void)getAdvertisingTrackingAuthority {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (@available(iOS 14, *)) {
|
||||
ATTrackingManagerAuthorizationStatus status = ATTrackingManager.trackingAuthorizationStatus;
|
||||
switch (status) {
|
||||
case ATTrackingManagerAuthorizationStatusDenied:
|
||||
// NSLog(@"用户拒绝IDFA");
|
||||
NSLog(@"用户拒绝IDFA");
|
||||
break;
|
||||
case ATTrackingManagerAuthorizationStatusAuthorized:
|
||||
// NSLog(@"用户允许IDFA");
|
||||
NSLog(@"用户允许IDFA");
|
||||
break;
|
||||
case ATTrackingManagerAuthorizationStatusNotDetermined: {
|
||||
// NSLog(@"用户未做选择或未弹窗IDFA");
|
||||
NSLog(@"用户未做选择或未弹窗IDFA");
|
||||
//请求弹出用户授权框,只会在程序运行是弹框1次,除非卸载app重装,通地图、相机等权限弹框一样
|
||||
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
|
||||
// NSLog(@"app追踪IDFA权限:%lu",(unsigned long)status);
|
||||
NSLog(@"app追踪IDFA权限:%lu",(unsigned long)status);
|
||||
}];
|
||||
}
|
||||
break;
|
||||
@@ -198,51 +145,66 @@ void qg_VAP_Logger_handler(VAPLogLevel level, const char* file, int line, const
|
||||
config.pushChatId = userId;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
///URL Scheme跳转
|
||||
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options{
|
||||
[[SocialShareManager sharedManager] handleURL:url];
|
||||
NSString *bundleID = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
|
||||
if ([url.scheme isEqualToString:[NSString stringWithFormat:@"line3rdp.%@",bundleID]]) {
|
||||
return [PILineLoginManager applicationOpenurl:app open:url];
|
||||
}
|
||||
|
||||
NSString *text = [url query];
|
||||
if(text.length){
|
||||
NSMutableDictionary *paramsDict = [NSMutableDictionary dictionary];
|
||||
NSArray *paramArray = [text componentsSeparatedByString:@"&"];
|
||||
for (NSString *param in paramArray) {
|
||||
if (param && param.length) {
|
||||
NSArray *parArr = [param componentsSeparatedByString:@"="];
|
||||
if (parArr.count == 2) {
|
||||
[paramsDict setObject:parArr[1] forKey:parArr[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(paramsDict[@"type"] != nil){
|
||||
NSInteger type = [paramsDict[@"type"] integerValue];
|
||||
if (type == 2) {
|
||||
NSString *uid = [NSString stringWithFormat:@"%@",paramsDict[@"uid"]];
|
||||
[[NSNotificationCenter defaultCenter]postNotificationName:kOpenRoomNotification object:nil userInfo:@{@"uid":uid}];
|
||||
ClientConfig *config = [ClientConfig shareConfig];
|
||||
config.roomId = uid;
|
||||
}else if(type == 7){
|
||||
NSString *uid = [NSString stringWithFormat:@"%@",paramsDict[@"uid"]];
|
||||
[[NSNotificationCenter defaultCenter]postNotificationName:kOpenRoomNotification object:nil userInfo:@{@"type":@"kOpenChat",@"uid":uid}];
|
||||
ClientConfig *config = [ClientConfig shareConfig];
|
||||
config.chatId = uid;
|
||||
}else if (type == 8){
|
||||
NSString *inviteCode = paramsDict[@"inviteCode"];
|
||||
if (inviteCode != nil && [[AccountInfoStorage instance]getUid].length == 0){
|
||||
ClientConfig *config = [ClientConfig shareConfig];
|
||||
config.inviteCode = inviteCode;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if([url.absoluteString containsString:@"fb1266232494209868"]){
|
||||
return [[FBSDKApplicationDelegate sharedInstance] application:app
|
||||
openURL:url
|
||||
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
|
||||
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
|
||||
}
|
||||
|
||||
return [GIDSignIn.sharedInstance handleURL:url];
|
||||
|
||||
|
||||
}
|
||||
|
||||
//- (void)__oldApplicationOpenURLMethod:(NSURL *)url {
|
||||
// NSString *text = [url query];
|
||||
// if(text.length){
|
||||
// NSMutableDictionary *paramsDict = [NSMutableDictionary dictionary];
|
||||
// NSArray *paramArray = [text componentsSeparatedByString:@"&"];
|
||||
// for (NSString *param in paramArray) {
|
||||
// if (param && param.length) {
|
||||
// NSArray *parArr = [param componentsSeparatedByString:@"="];
|
||||
// if (parArr.count == 2) {
|
||||
// [paramsDict setObject:parArr[1] forKey:parArr[0]];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(paramsDict[@"type"] != nil){
|
||||
// NSInteger type = [paramsDict[@"type"] integerValue];
|
||||
// if (type == 2) {
|
||||
// NSString *uid = [NSString stringWithFormat:@"%@",paramsDict[@"uid"]];
|
||||
// [[NSNotificationCenter defaultCenter]postNotificationName:kOpenRoomNotification object:nil userInfo:@{@"uid":uid}];
|
||||
// ClientConfig *config = [ClientConfig shareConfig];
|
||||
// config.roomId = uid;
|
||||
// }else if(type == 7){
|
||||
// NSString *uid = [NSString stringWithFormat:@"%@",paramsDict[@"uid"]];
|
||||
// [[NSNotificationCenter defaultCenter]postNotificationName:kOpenRoomNotification object:nil userInfo:@{@"type":@"kOpenChat",@"uid":uid}];
|
||||
// ClientConfig *config = [ClientConfig shareConfig];
|
||||
// config.chatId = uid;
|
||||
// }else if (type == 8){
|
||||
// NSString *inviteCode = paramsDict[@"inviteCode"];
|
||||
// if (inviteCode != nil && [[AccountInfoStorage instance]getUid].length == 0){
|
||||
// ClientConfig *config = [ClientConfig shareConfig];
|
||||
// config.inviteCode = inviteCode;
|
||||
// }
|
||||
// }
|
||||
//// return YES;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
#pragma mark - Core Data stack
|
||||
@synthesize managedObjectContext = _managedObjectContext;
|
||||
@@ -284,7 +246,7 @@ void qg_VAP_Logger_handler(VAPLogLevel level, const char* file, int line, const
|
||||
error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
|
||||
// Replace this with code to handle the error appropriately.
|
||||
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
// NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
abort();
|
||||
}
|
||||
|
||||
@@ -316,7 +278,7 @@ void qg_VAP_Logger_handler(VAPLogLevel level, const char* file, int line, const
|
||||
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
// NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
16
YuMi/Appdelegate/FBSDKSettings+PISDKSettings.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// FBSDKSettings+PISDKSettings.h
|
||||
// YuMi
|
||||
//
|
||||
// Created by duoban on 2023/9/22.
|
||||
//
|
||||
|
||||
#import <FBSDKCoreKit/FBSDKCoreKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FBSDKSettings (PISDKSettings)
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
19
YuMi/Appdelegate/FBSDKSettings+PISDKSettings.m
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// FBSDKSettings+PISDKSettings.m
|
||||
// YuMi
|
||||
//
|
||||
// Created by duoban on 2023/9/22.
|
||||
//
|
||||
|
||||
#import "FBSDKSettings+PISDKSettings.h"
|
||||
#import "GULAppDelegateSwizzler.h"
|
||||
|
||||
|
||||
@implementation FBSDKSettings (PISDKSettings)
|
||||
///必须为FBSDKSettings添加setAutoLogAppEventsEnabled方法,不然分享时会闪退
|
||||
+(void)setAutoLogAppEventsEnabled:(BOOL)is{
|
||||
|
||||
}
|
||||
|
||||
|
||||
@end
|
Before Width: | Height: | Size: 98 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "1@3x (1).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "椭圆 6@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 41 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "组 4.jpg@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 242 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "组 4.jpg@3x-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 246 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "moli_money_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "组 7122@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 82 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "1@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_flag_lv_1@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 47 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_flag_lv_2@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 49 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_flag_lv_3@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 47 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_flag_lv_4@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 48 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_flag_lv_5@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 48 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_flag_lv_6@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 49 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_head_lv_2@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 150 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_head_lv_3@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 166 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_head_lv_4@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 151 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_head_lv_5@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 192 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_head_lv_6@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 193 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_avatar_heart@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "容器 8171@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 519 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "框@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
YuMi/Assets.xcassets/1.0.16/cp_bg.imageset/框@3x.png
vendored
Before Width: | Height: | Size: 196 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 32@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "头像@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 9.8 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "爱心@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 101 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "返回 1@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 39 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "3@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 46 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "3@3x-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 39 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "3@3x-3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 42 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "3@3x-4.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 31 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "3@3x-5.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 32@3x-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 41 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 31@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 601 B |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_relationship_bg@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 199 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_relationship_lv_0@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_relationship_lv_1@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_relationship_lv_2@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_relationship_lv_3@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_relationship_lv_4@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "cp_relationship_lv_5@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 45@3x-3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 45@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 1.5 KiB |