diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index ffd5ccb..72d6528 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -8,6 +8,14 @@ #define KBAPI_h +#ifndef SUCCESS_CODE +#define SUCCESS_CODE 200 +#endif +#ifndef ERROR_CODE +#define ERROR_CODE 500 +#endif + + // 兼容旧命名(如有使用 API_APPLE_LOGIN 的位置,映射到统一命名) #define API_APPLE_LOGIN @"/user/appleLogin" // Apple 登录 diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 034b009..a8d8b35 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 04122F6E2EC5F40800EF7AB3 /* FGIAPProductsFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F652EC5F40800EF7AB3 /* FGIAPProductsFilter.m */; }; 04122F6F2EC5F40800EF7AB3 /* FGIAPManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F632EC5F40800EF7AB3 /* FGIAPManager.m */; }; 04122F702EC5F40800EF7AB3 /* FGIAPService.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F672EC5F40800EF7AB3 /* FGIAPService.m */; }; + 04122F7E2EC5FC5500EF7AB3 /* KBJfPayCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F7C2EC5FC5500EF7AB3 /* KBJfPayCell.m */; }; + 04122F822EC5FC6F00EF7AB3 /* KBJfPay.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F802EC5FC6F00EF7AB3 /* KBJfPay.m */; }; 043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */; }; 0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */; }; 0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; @@ -197,6 +199,10 @@ 04122F692EC5F40800EF7AB3 /* FGIAPVerifyTransaction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FGIAPVerifyTransaction.h; sourceTree = ""; }; 04122F6A2EC5F40800EF7AB3 /* NSObject+FGIsNullOrEmpty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+FGIsNullOrEmpty.h"; sourceTree = ""; }; 04122F6B2EC5F40800EF7AB3 /* NSObject+FGIsNullOrEmpty.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+FGIsNullOrEmpty.m"; sourceTree = ""; }; + 04122F7B2EC5FC5500EF7AB3 /* KBJfPayCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBJfPayCell.h; sourceTree = ""; }; + 04122F7C2EC5FC5500EF7AB3 /* KBJfPayCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBJfPayCell.m; sourceTree = ""; }; + 04122F7F2EC5FC6F00EF7AB3 /* KBJfPay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBJfPay.h; sourceTree = ""; }; + 04122F802EC5FC6F00EF7AB3 /* KBJfPay.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBJfPay.m; sourceTree = ""; }; 0459D1B22EBA284C00F2D189 /* KBSkinCenterVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinCenterVC.h; sourceTree = ""; }; 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinCenterVC.m; sourceTree = ""; }; 0459D1B52EBA287900F2D189 /* KBSkinManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinManager.h; sourceTree = ""; }; @@ -482,35 +488,29 @@ path = VM; sourceTree = ""; }; - 04122F5E2EC5F3DF00EF7AB3 /* M */ = { + 04122F612EC5F3DF00EF7AB3 /* Pay */ = { + isa = PBXGroup; + children = ( + 04122F812EC5FC6F00EF7AB3 /* VC */, + 04122F7D2EC5FC5500EF7AB3 /* V */, + 04122F642EC5F40600EF7AB3 /* M */, + 04122F652EC5F40600EF7AB3 /* VM */, + ); + path = Pay; + sourceTree = ""; + }; + 04122F642EC5F40600EF7AB3 /* M */ = { isa = PBXGroup; children = ( ); path = M; sourceTree = ""; }; - 04122F5F2EC5F3DF00EF7AB3 /* V */ = { + 04122F652EC5F40600EF7AB3 /* VM */ = { isa = PBXGroup; children = ( ); - path = V; - sourceTree = ""; - }; - 04122F602EC5F3DF00EF7AB3 /* VC */ = { - isa = PBXGroup; - children = ( - ); - path = VC; - sourceTree = ""; - }; - 04122F612EC5F3DF00EF7AB3 /* Pay */ = { - isa = PBXGroup; - children = ( - 04122F5E2EC5F3DF00EF7AB3 /* M */, - 04122F5F2EC5F3DF00EF7AB3 /* V */, - 04122F602EC5F3DF00EF7AB3 /* VC */, - ); - path = Pay; + path = VM; sourceTree = ""; }; 04122F6C2EC5F40800EF7AB3 /* FGIAPService */ = { @@ -530,6 +530,24 @@ path = FGIAPService; sourceTree = ""; }; + 04122F7D2EC5FC5500EF7AB3 /* V */ = { + isa = PBXGroup; + children = ( + 04122F7B2EC5FC5500EF7AB3 /* KBJfPayCell.h */, + 04122F7C2EC5FC5500EF7AB3 /* KBJfPayCell.m */, + ); + path = V; + sourceTree = ""; + }; + 04122F812EC5FC6F00EF7AB3 /* VC */ = { + isa = PBXGroup; + children = ( + 04122F7F2EC5FC6F00EF7AB3 /* KBJfPay.h */, + 04122F802EC5FC6F00EF7AB3 /* KBJfPay.m */, + ); + path = VC; + sourceTree = ""; + }; 0477BD942EBAFF4E0055D639 /* Utils */ = { isa = PBXGroup; children = ( @@ -1215,33 +1233,6 @@ path = Login; sourceTree = ""; }; - 04122F612EC5F3DF00EF7AB3 /* Pay */ = { - isa = PBXGroup; - children = ( - 04122F642EC5F40600EF7AB3 /* M */, - 04122F652EC5F40600EF7AB3 /* VM */, - ); - path = Pay; - sourceTree = ""; - }; - 04122F642EC5F40600EF7AB3 /* M */ = { - isa = PBXGroup; - children = ( - 04122F7B2EC6123500EF7AB3 /* IAPVerifyTransactionObj.h */, - 04122F7C2EC6123500EF7AB3 /* IAPVerifyTransactionObj.m */, - ); - path = M; - sourceTree = ""; - }; - 04122F652EC5F40600EF7AB3 /* VM */ = { - isa = PBXGroup; - children = ( - 04122F782EC610C500EF7AB3 /* PayVM.h */, - 04122F792EC610C500EF7AB3 /* PayVM.m */, - ); - path = VM; - sourceTree = ""; - }; 04FC95EE2EB3399D007BD342 /* Manager */ = { isa = PBXGroup; children = ( @@ -1568,8 +1559,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 04122F7D2EC6123500EF7AB3 /* IAPVerifyTransactionObj.m in Sources */, - 04122F7A2EC610C500EF7AB3 /* PayVM.m in Sources */, 04122F622EC5F41D00EF7AB3 /* KBUser.m in Sources */, 049FB31D2EC21BCD00FAB05D /* KBMyKeyboardCell.m in Sources */, 048909F62EC0AAAA00FABA60 /* KBCategoryTitleCell.m in Sources */, @@ -1599,6 +1588,7 @@ 048908D22EBF611D00FABA60 /* KBHistoryMoreCell.m in Sources */, 04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */, 0477BDF72EBC63A80055D639 /* KBTestVC.m in Sources */, + 04122F7E2EC5FC5500EF7AB3 /* KBJfPayCell.m in Sources */, 049FB2402EC4B6EF00FAB05D /* KBULBridge.m in Sources */, 04FC95C92EB1E4C9007BD342 /* BaseNavigationController.m in Sources */, 048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */, @@ -1655,6 +1645,7 @@ A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */, 047C65532EBCBAC60035E841 /* KBCommunityVC.m in Sources */, A1B2C4212EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */, + 04122F822EC5FC6F00EF7AB3 /* KBJfPay.m in Sources */, 04122F5D2EC5E5A900EF7AB3 /* KBLoginVM.m in Sources */, 0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */, 048908E32EBF760000FABA60 /* MySkinCell.m in Sources */, diff --git a/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/Contents.json b/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/Contents.json new file mode 100644 index 0000000..abb97a3 --- /dev/null +++ b/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "pay_big_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "pay_big_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/pay_big_icon@2x.png b/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/pay_big_icon@2x.png new file mode 100644 index 0000000..f0277ca Binary files /dev/null and b/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/pay_big_icon@2x.png differ diff --git a/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/pay_big_icon@3x.png b/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/pay_big_icon@3x.png new file mode 100644 index 0000000..eca8414 Binary files /dev/null and b/keyBoard/Assets.xcassets/My/pay_big_icon.imageset/pay_big_icon@3x.png differ diff --git a/keyBoard/Class/Home/V/HomeHeadView.m b/keyBoard/Class/Home/V/HomeHeadView.m index a97db12..bfce22e 100644 --- a/keyBoard/Class/Home/V/HomeHeadView.m +++ b/keyBoard/Class/Home/V/HomeHeadView.m @@ -8,6 +8,7 @@ #import "HomeHeadView.h" #import "UIImage+KBColor.h" #import "KBTopImageButton.h" +#import "KBJfPay.h" @interface HomeHeadView() @@ -161,7 +162,9 @@ #pragma mark - Actions - (void)onTapBuyAction { - if (self.onTapBuy) { self.onTapBuy(); } +// if (self.onTapBuy) { self.onTapBuy(); } +// KBJfPay *vc = [[KBJfPay alloc] init]; +// [KB_CURRENT_NAV pushViewController:vc animated:true]; } #pragma mark - Lazy diff --git a/keyBoard/Class/Pay/V/KBJfPayCell.h b/keyBoard/Class/Pay/V/KBJfPayCell.h new file mode 100644 index 0000000..0a9850f --- /dev/null +++ b/keyBoard/Class/Pay/V/KBJfPayCell.h @@ -0,0 +1,21 @@ +// +// KBJfPayCell.h +// 积分充值选项 Cell(自定义样式) +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBJfPayCell : UICollectionViewCell + +/// 配置展示:金币数量 + 价格 +- (void)configCoins:(NSString *)coins price:(NSString *)price; + +/// 刷新选中态(带动画可选) +- (void)applySelected:(BOOL)selected animated:(BOOL)animated; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/keyBoard/Class/Pay/V/KBJfPayCell.m b/keyBoard/Class/Pay/V/KBJfPayCell.m new file mode 100644 index 0000000..cd6f901 --- /dev/null +++ b/keyBoard/Class/Pay/V/KBJfPayCell.m @@ -0,0 +1,232 @@ +// +// KBJfPayCell.m +// 自定义充值选项 Cell +// - 顶部金币图 + 数字 +// - 底部价格 +// - 选中态绿色描边 + 底部小圆勾 +// + +#import "KBJfPayCell.h" + +// 用 CAShapeLayer 画底部的小圆 + 对勾 +@interface KBPayCheckBadgeView : UIView +@property (nonatomic, assign) BOOL checked; +@end + +@implementation KBPayCheckBadgeView { + CAShapeLayer *_circle; + CAShapeLayer *_check; +} + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.backgroundColor = UIColor.clearColor; + _circle = [CAShapeLayer layer]; + _circle.fillColor = [UIColor colorWithHex:KBColorValue].CGColor; // 主题绿填充 + [self.layer addSublayer:_circle]; + + _check = [CAShapeLayer layer]; + _check.strokeColor = UIColor.whiteColor.CGColor; + _check.fillColor = UIColor.clearColor.CGColor; + _check.lineWidth = 2.0; + _check.lineCap = kCALineCapRound; + _check.lineJoin = kCALineJoinRound; + [self.layer addSublayer:_check]; + self.hidden = YES; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + CGFloat s = MIN(self.bounds.size.width, self.bounds.size.height); + CGRect r = CGRectMake((self.bounds.size.width - s) * 0.5, (self.bounds.size.height - s) * 0.5, s, s); + UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:r]; + _circle.path = circle.CGPath; + + // 对勾路径 + CGPoint p1 = CGPointMake(CGRectGetMinX(r) + s*0.26, CGRectGetMinY(r) + s*0.55); + CGPoint p2 = CGPointMake(CGRectGetMinX(r) + s*0.45, CGRectGetMinY(r) + s*0.74); + CGPoint p3 = CGPointMake(CGRectGetMinX(r) + s*0.76, CGRectGetMinY(r) + s*0.26); + UIBezierPath *check = [UIBezierPath bezierPath]; + [check moveToPoint:p1]; + [check addLineToPoint:p2]; + [check addLineToPoint:p3]; + _check.path = check.CGPath; +} + +@end + +@interface KBJfPayCell () +@property (nonatomic, strong) UIView *cardView; // 卡片背景(圆角) +@property (nonatomic, strong) UIStackView *topStack; // 顶部水平栈:金币 + 数字(整体居中) +@property (nonatomic, strong) UIImageView *iconView; // 左侧金币图 +@property (nonatomic, strong) UILabel *coinLabel; // 690 +@property (nonatomic, strong) UILabel *priceLabel; // $6.90 + +@property (nonatomic, strong) KBPayCheckBadgeView *badge; // 底部小圆勾 +@property (nonatomic, strong) CAShapeLayer *borderLayer; // 选中态描边 +@property (nonatomic, strong) CAShapeLayer *shadowLayer; // 选中态下方阴影 +@end + +@implementation KBJfPayCell + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.contentView.backgroundColor = UIColor.clearColor; + + [self.contentView addSubview:self.cardView]; + // 顶部金币 + 数字放入一个水平栈,保证整体居中 + [self.cardView addSubview:self.topStack]; + [self.topStack addArrangedSubview:self.iconView]; + [self.topStack addArrangedSubview:self.coinLabel]; + [self.cardView addSubview:self.priceLabel]; + [self.contentView addSubview:self.badge]; + + [self.cardView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; + [self.topStack mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.cardView).offset(16); + make.centerX.equalTo(self.cardView); // 整体水平居中 + }]; + [self.iconView mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.height.mas_equalTo(18); + }]; + [self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.equalTo(self.cardView); + make.top.equalTo(self.topStack.mas_bottom).offset(8); + }]; + [self.badge mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.equalTo(self.cardView); + make.bottom.equalTo(self.cardView).offset(12); // 超出一点,营造视觉效果 + make.width.height.mas_equalTo(20); + }]; + + // 选中态描边 + self.borderLayer = [CAShapeLayer layer]; + self.borderLayer.strokeColor = [UIColor colorWithHex:KBColorValue].CGColor; + self.borderLayer.fillColor = UIColor.clearColor.CGColor; + self.borderLayer.lineWidth = 2.0; + self.borderLayer.hidden = YES; + [self.cardView.layer addSublayer:self.borderLayer]; + + // 外边框阴影(向下投影,主题绿) + self.shadowLayer = [CAShapeLayer layer]; + self.shadowLayer.fillColor = UIColor.clearColor.CGColor; // 不填充,仅投影 + self.shadowLayer.strokeColor = UIColor.clearColor.CGColor; + self.shadowLayer.shadowColor = [UIColor colorWithHex:KBColorValue].CGColor; // 0x02BEAC + self.shadowLayer.shadowOpacity = 0.45; // 透明度 + self.shadowLayer.shadowOffset = CGSizeMake(0, 6); // 向下 + self.shadowLayer.shadowRadius = 8.0; // 模糊半径 + self.shadowLayer.hidden = YES; + // 放在 contentView 最底层,避免遮挡内容 + [self.contentView.layer insertSublayer:self.shadowLayer atIndex:0]; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + CGFloat radius = 18; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.cardView.bounds cornerRadius:radius]; + self.cardView.layer.cornerRadius = radius; + self.cardView.layer.masksToBounds = YES; // 内容裁剪,阴影单独用 shadowLayer 实现 + self.borderLayer.path = path.CGPath; + self.borderLayer.frame = self.cardView.bounds; + + // 阴影路径与卡片一致(放在 contentView 坐标系下) + CGRect cardRectInContent = self.cardView.frame; + UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:cardRectInContent cornerRadius:radius]; + self.shadowLayer.frame = self.contentView.bounds; + self.shadowLayer.shadowPath = shadowPath.CGPath; +} + +- (void)prepareForReuse { + [super prepareForReuse]; + [self applySelected:NO animated:NO]; +} + +- (void)setSelected:(BOOL)selected { + [super setSelected:selected]; + // 同步系统选中态到自定义 UI(保证通过 selectItemAtIndexPath 设置时也能出现描边/阴影) + [self applySelected:selected animated:NO]; +} + +- (void)configCoins:(NSString *)coins price:(NSString *)price { + self.coinLabel.text = coins.length ? coins : @"690"; + self.priceLabel.text = price.length ? price : @"$6.90"; +} + +- (void)applySelected:(BOOL)selected animated:(BOOL)animated { + self.borderLayer.hidden = !selected; + self.badge.hidden = !selected; + self.shadowLayer.hidden = !selected; // 选中时显示阴影 + void (^animations)(void) = ^{ + self.cardView.layer.shadowOpacity = selected ? 0.0 : 0.0; // 保持简洁 + }; + if (animated) { + [UIView animateWithDuration:0.15 animations:animations]; + } else { + animations(); + } +} + +#pragma mark - Lazy UI +- (UIView *)cardView { + if (!_cardView) { + _cardView = [UIView new]; + // 淡灰渐变效果可用切图/渐变,这里简化为纯色 + _cardView.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1.0]; + } + return _cardView; +} + +- (UIImageView *)iconView { + if (!_iconView) { + _iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_jb_icon"]]; + _iconView.contentMode = UIViewContentModeScaleAspectFit; + } + return _iconView; +} + +- (UILabel *)coinLabel { + if (!_coinLabel) { + _coinLabel = [UILabel new]; + _coinLabel.text = @"690"; + _coinLabel.font = [UIFont systemFontOfSize:20 weight:UIFontWeightBold]; + _coinLabel.textColor = [UIColor colorWithHex:KBBlackValue]; + } + return _coinLabel; +} + +- (UILabel *)priceLabel { + if (!_priceLabel) { + _priceLabel = [UILabel new]; + _priceLabel.text = @"$6.90"; + _priceLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold]; + _priceLabel.textColor = [UIColor colorWithHex:KBColorValue]; + } + return _priceLabel; +} + +- (KBPayCheckBadgeView *)badge { + if (!_badge) { + _badge = [[KBPayCheckBadgeView alloc] initWithFrame:CGRectZero]; + _badge.userInteractionEnabled = NO; + } + return _badge; +} + +- (UIStackView *)topStack { + if (!_topStack) { + _topStack = [[UIStackView alloc] initWithArrangedSubviews:@[]]; + _topStack.axis = UILayoutConstraintAxisHorizontal; // 水平 + _topStack.alignment = UIStackViewAlignmentCenter; + _topStack.spacing = 6; + _topStack.distribution = UIStackViewDistributionFill; + } + return _topStack; +} + +@end diff --git a/keyBoard/Class/Pay/VC/KBJfPay.h b/keyBoard/Class/Pay/VC/KBJfPay.h new file mode 100644 index 0000000..1ce3de1 --- /dev/null +++ b/keyBoard/Class/Pay/VC/KBJfPay.h @@ -0,0 +1,16 @@ +// +// KBJfPay.h +// keyBoard +// +// Created by Mac on 2025/11/13. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBJfPay : BaseViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Pay/VC/KBJfPay.m b/keyBoard/Class/Pay/VC/KBJfPay.m new file mode 100644 index 0000000..c72651f --- /dev/null +++ b/keyBoard/Class/Pay/VC/KBJfPay.m @@ -0,0 +1,359 @@ +// +// KBJfPay.m +// keyBoard + +#import "KBJfPay.h" +#import "KBJfPayCell.h" + +static NSString * const kKBJfPayCellId = @"kKBJfPayCellId"; + +@interface KBJfPay () + +@property (nonatomic, strong) UIImageView *bgImageView; // 全屏背景图 + + +// 顶部信息 +@property (nonatomic, strong) UILabel *myPointsTitleLabel; // “My Points” +@property (nonatomic, strong) UILabel *pointsLabel; // 积分数值 +@property (nonatomic, strong) UIImageView *bigCoinImageView; // 右上装饰大金币 pay_big_icon + +// “Recharge Now” 小标题 +@property (nonatomic, strong) UIImageView *smallLeftIcon; // 左侧小金币 shop_jb_icon +@property (nonatomic, strong) UILabel *rechargeLabel; // “Recharge Now” + +// 列表容器(因为需要只有左上/右上圆角) +@property (nonatomic, strong) UIView *listContainerView; // 承载 collectionView +@property (nonatomic, strong) UICollectionView *collectionView; + +// 底部按钮/协议 +@property (nonatomic, strong) UIButton *payButton; // 充值按钮 +@property (nonatomic, strong) UILabel *agreementLabel; // 协议提示 +@property (nonatomic, strong) UIButton *agreementButton; + +// 数据 +@property (nonatomic, strong) NSArray *data; // 简单演示数据:@{coins, price} +@property (nonatomic, assign) NSInteger selectedIndex; // 当前选中项 + +@end + +@implementation KBJfPay + +- (void)viewDidLoad { + [super viewDidLoad]; + self.bgImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"my_bg_icon"]]; + self.bgImageView.contentMode = UIViewContentModeScaleAspectFill; + self.kb_navView.backgroundColor = [UIColor clearColor]; + [self.view insertSubview:self.bgImageView belowSubview:self.kb_navView]; + [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.view); + }]; + self.kb_titleLabel.text = @"Recharge"; + + // 默认数据(演示) + self.data = @[ + @{ @"coins": @690, @"price": @"$6.90" }, + @{ @"coins": @1280, @"price": @"$12.90" }, + @{ @"coins": @3290, @"price": @"$32.90" }, + @{ @"coins": @4990, @"price": @"$49.90" }, + @{ @"coins": @9990, @"price": @"$99.90" }, + @{ @"coins": @19990,@"price": @"$199.90" }, + ]; + self.selectedIndex = 1; // 默认选中第二个,贴近截图 + + // 视图组装 + [self.view addSubview:self.myPointsTitleLabel]; + [self.view addSubview:self.pointsLabel]; + + [self.view addSubview:self.listContainerView]; + [self.view addSubview:self.bigCoinImageView]; + [self.listContainerView addSubview:self.smallLeftIcon]; + [self.listContainerView addSubview:self.rechargeLabel]; + [self.listContainerView addSubview:self.collectionView]; + [self.view addSubview:self.payButton]; + [self.view addSubview:self.agreementLabel]; + [self.view addSubview:self.agreementButton]; + + + // 布局(mas) + [self.myPointsTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.view).offset(16); + make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT + 40); + }]; + [self.pointsLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.myPointsTitleLabel); + make.top.equalTo(self.myPointsTitleLabel.mas_bottom).offset(4); + }]; + [self.bigCoinImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.view).offset(-12); + make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT + 10); + make.width.mas_equalTo(131); + make.height.mas_equalTo(144); + }]; + + // 列表容器 + 圆角(仅左上/右上) + [self.listContainerView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.equalTo(self.view); + make.top.mas_equalTo(KB_NAV_TOTAL_HEIGHT + 123); + make.bottom.equalTo(self.payButton.mas_top).offset(-16); + make.height.greaterThanOrEqualTo(@220); + }]; + + [self.smallLeftIcon mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.listContainerView).offset(16); + make.top.equalTo(self.listContainerView).offset(16); + make.width.height.mas_equalTo(20); + }]; + [self.rechargeLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.smallLeftIcon); + make.left.equalTo(self.smallLeftIcon.mas_right).offset(8); + }]; + + + [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { + make.bottom.left.right.equalTo(self.listContainerView).inset(16); + make.top.equalTo(self.smallLeftIcon.mas_bottom).offset(19); + }]; + + [self.agreementButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.equalTo(self.view); + make.bottom.equalTo(self.view).offset(-KB_SAFE_BOTTOM - 15); + }]; + [self.agreementLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.equalTo(self.view); + make.bottom.equalTo(self.agreementButton.mas_top).offset(-8); + }]; + + // 底部按钮 + [self.payButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.view).offset(24); + make.right.equalTo(self.view).offset(-24); + make.bottom.equalTo(self.agreementLabel.mas_top).offset(-14); + make.height.mas_equalTo(58); + }]; + + + // 刷新 + [self.collectionView reloadData]; + + // 确保首次进入就出现选中态外边框与阴影 + dispatch_async(dispatch_get_main_queue(), ^{ + NSIndexPath *ip = [NSIndexPath indexPathForItem:self.selectedIndex inSection:0]; + // 让系统层面也处于选中态,便于 setSelected 同步 UI + if (ip) { + [self.collectionView selectItemAtIndexPath:ip animated:NO scrollPosition:UICollectionViewScrollPositionNone]; + } + KBJfPayCell *cell = (KBJfPayCell *)[self.collectionView cellForItemAtIndexPath:ip]; + if (cell) { [cell applySelected:YES animated:NO]; } + }); +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + // 再兜底一次(某些布局时机下,首屏 reload 后 cell 还未可见) + NSIndexPath *ip = [NSIndexPath indexPathForItem:self.selectedIndex inSection:0]; + if (ip) { + [self.collectionView selectItemAtIndexPath:ip animated:NO scrollPosition:UICollectionViewScrollPositionNone]; + } + KBJfPayCell *cell = (KBJfPayCell *)[self.collectionView cellForItemAtIndexPath:ip]; + if (cell) { [cell applySelected:YES animated:NO]; } +} + +#pragma mark - UICollectionView Delegate (ensure first show) +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + if (![cell isKindOfClass:KBJfPayCell.class]) { return; } + KBJfPayCell *c = (KBJfPayCell *)cell; + BOOL sel = (indexPath.item == self.selectedIndex); + if (sel) { + [collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; + } + [c applySelected:sel animated:NO]; +} + +#pragma mark - 圆角蒙版 +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + // 仅左上、右上圆角 + UIRectCorner corners = UIRectCornerTopLeft | UIRectCornerTopRight; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.listContainerView.bounds + byRoundingCorners:corners + cornerRadii:CGSizeMake(20, 20)]; + CAShapeLayer *mask = [CAShapeLayer layer]; + mask.frame = self.listContainerView.bounds; + mask.path = path.CGPath; + self.listContainerView.layer.mask = mask; +} + +#pragma mark - UICollectionView +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.data.count; +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + KBJfPayCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBJfPayCellId forIndexPath:indexPath]; + NSDictionary *item = self.data[indexPath.item]; + NSString *coins = [NSString stringWithFormat:@"%@", item[@"coins"]]; + NSString *price = item[@"price"]; // 形如 "$6.90" + [cell configCoins:coins price:price]; + [cell applySelected:(indexPath.item == self.selectedIndex) animated:NO]; + return cell; +} + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + if (self.selectedIndex == indexPath.item) { return; } + NSInteger old = self.selectedIndex; + self.selectedIndex = indexPath.item; + + KBJfPayCell *newCell = (KBJfPayCell *)[collectionView cellForItemAtIndexPath:indexPath]; + [newCell applySelected:YES animated:YES]; + if (old >= 0 && old < self.data.count) { + NSIndexPath *oldIP = [NSIndexPath indexPathForItem:old inSection:0]; + KBJfPayCell *oldCell = (KBJfPayCell *)[collectionView cellForItemAtIndexPath:oldIP]; + [oldCell applySelected:NO animated:YES]; + } +} + +// 三列网格 +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + CGFloat totalW = collectionView.bounds.size.width; + CGFloat spacing = 10.0; // 列间距 + CGFloat columns = 3.0; + CGFloat insets = 0; // 已在 mas 中留了左右 16,这里内部 cell 不额外 inset + CGFloat w = floor((totalW - insets - spacing * (columns - 1)) / columns); + CGFloat h = KBFit(116); + return CGSizeMake(MAX(0, w), h); +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { + return 10.0; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { + return 30; +} + +#pragma mark - Actions +- (void)onTapPayButton { + // 这里只做 UI,实际支付逻辑由调用方接入 + if (self.selectedIndex >= 0 && self.selectedIndex < self.data.count) { + NSDictionary *item = self.data[self.selectedIndex]; + NSString *msg = [NSString stringWithFormat:@"购买:%@ Coins %@", item[@"coins"], item[@"price"]]; + [KBHUD showInfo:msg]; + } +} + +- (void)agreementButtonAction{ + [KBHUD showInfo:@"跳转协议"]; +} + +#pragma mark - Lazy UI +- (UILabel *)myPointsTitleLabel { + if (!_myPointsTitleLabel) { + _myPointsTitleLabel = [UILabel new]; + _myPointsTitleLabel.text = @"My Points"; + _myPointsTitleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold]; + _myPointsTitleLabel.textColor = [UIColor colorWithHex:KBBlackValue]; + } + return _myPointsTitleLabel; +} + +- (UILabel *)pointsLabel { + if (!_pointsLabel) { + _pointsLabel = [UILabel new]; + _pointsLabel.text = @"4230"; // 示例值 + _pointsLabel.font = [UIFont systemFontOfSize:36 weight:UIFontWeightBold]; + _pointsLabel.textColor = [UIColor colorWithHex:KBColorValue]; + } + return _pointsLabel; +} + +- (UIImageView *)bigCoinImageView { + if (!_bigCoinImageView) { + _bigCoinImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_big_icon"]]; + _bigCoinImageView.contentMode = UIViewContentModeScaleAspectFit; + } + return _bigCoinImageView; +} + +- (UIImageView *)smallLeftIcon { + if (!_smallLeftIcon) { + _smallLeftIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_jb_icon"]]; + _smallLeftIcon.contentMode = UIViewContentModeScaleAspectFit; + } + return _smallLeftIcon; +} + +- (UILabel *)rechargeLabel { + if (!_rechargeLabel) { + _rechargeLabel = [UILabel new]; + _rechargeLabel.text = @"Recharge Now"; + _rechargeLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold]; + _rechargeLabel.textColor = [UIColor colorWithHex:KBBlackValue]; + } + return _rechargeLabel; +} + +- (UIView *)listContainerView { + if (!_listContainerView) { + _listContainerView = [UIView new]; + // 轻微底色,突出圆角区域(也可用渐变,按需) + _listContainerView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.43]; + } + return _listContainerView; +} + +- (UICollectionView *)collectionView { + if (!_collectionView) { + UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; + layout.scrollDirection = UICollectionViewScrollDirectionVertical; + _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + _collectionView.backgroundColor = UIColor.clearColor; + _collectionView.dataSource = self; + _collectionView.delegate = self; + _collectionView.alwaysBounceVertical = YES; + [_collectionView registerClass:KBJfPayCell.class forCellWithReuseIdentifier:kKBJfPayCellId]; + _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + return _collectionView; +} + +- (UIButton *)payButton { + if (!_payButton) { + _payButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_payButton setTitle:@"Recharge Now" forState:UIControlStateNormal]; + [_payButton setTitleColor:[UIColor colorWithHex:KBBlackValue] forState:UIControlStateNormal]; + _payButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold]; + // 使用现有的切图(若不存在可退化为渐变图片) + UIImage *bg = [UIImage imageNamed:@"recharge_now_icon"]; + if (bg) { + [_payButton setBackgroundImage:bg forState:UIControlStateNormal]; + } else { + UIImage *fallback = [UIImage kb_gradientImageWithColors:@[[UIColor colorWithHex:0xC7F8F0], [UIColor colorWithHex:0xE8FFF6]] size:CGSizeMake(10, 58) direction:KBGradientDirectionLeftToRight]; + [_payButton setBackgroundImage:[fallback resizableImageWithCapInsets:UIEdgeInsetsMake(29, 29, 29, 29) resizingMode:UIImageResizingModeStretch] forState:UIControlStateNormal]; + } + [_payButton addTarget:self action:@selector(onTapPayButton) forControlEvents:UIControlEventTouchUpInside]; + } + return _payButton; +} + +- (UILabel *)agreementLabel { + if (!_agreementLabel) { + _agreementLabel = [UILabel new]; + _agreementLabel.text = @"By clicking Pay, you indicate your agreement to the"; // 简化文案 + _agreementLabel.font = [UIFont systemFontOfSize:11 weight:UIFontWeightRegular]; + _agreementLabel.textColor = [UIColor colorWithWhite:0.45 alpha:1.0]; + } + return _agreementLabel; +} +- (UIButton *)agreementButton { + if (!_agreementButton) { + _agreementButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_agreementButton setTitle:@"《Embership Agreement》" forState:UIControlStateNormal]; + [_agreementButton setTitleColor:[UIColor colorWithHex:KBColorValue] forState:UIControlStateNormal]; + _agreementButton.titleLabel.font = [UIFont systemFontOfSize:10 weight:UIFontWeightSemibold]; + + [_agreementButton addTarget:self action:@selector(agreementButtonAction) forControlEvents:UIControlEventTouchUpInside]; + } + return _agreementButton; +} + +@end diff --git a/keyBoard/Class/Pay/VM/PayVM.h b/keyBoard/Class/Pay/VM/PayVM.h index 2046b78..125669a 100644 --- a/keyBoard/Class/Pay/VM/PayVM.h +++ b/keyBoard/Class/Pay/VM/PayVM.h @@ -11,12 +11,7 @@ NS_ASSUME_NONNULL_BEGIN typedef void(^KBPayCompletion)(NSInteger sta, NSString * _Nullable msg); /// 统一状态码(与原 Swift 代码的 succCode / errorCode 语义一致) -#ifndef KB_PAY_SUCC_CODE -#define KB_PAY_SUCC_CODE 0 -#endif -#ifndef KB_PAY_ERROR_CODE -#define KB_PAY_ERROR_CODE 1 -#endif + @interface PayVM : NSObject diff --git a/keyBoard/KeyBoardPrefixHeader.pch b/keyBoard/KeyBoardPrefixHeader.pch index 32cd06e..1f01bd4 100644 --- a/keyBoard/KeyBoardPrefixHeader.pch +++ b/keyBoard/KeyBoardPrefixHeader.pch @@ -124,6 +124,12 @@ static inline CGFloat KB_StatusBarHeight(void) { #define KB_TABBAR_HEIGHT (KB_TABBAR_BASE_HEIGHT + KB_SafeAreaBottom()) #define KB_IS_IPHONEX_SERIES (KB_SafeAreaBottom() > 0.0) +// 屏幕底部安全区高度(Home 指示条区域高度)。 +// 说明:带刘海/异形屏通常为 34,非异形屏为 0;随横竖屏、安全区变动而变。 +#ifndef KB_SAFE_BOTTOM +#define KB_SAFE_BOTTOM (KB_SafeAreaBottom()) +#endif +