Files
keyboard/keyBoard/Class/Pay/VC/KBJfPay.m
2025-12-12 14:46:38 +08:00

425 lines
18 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

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

//
// KBJfPay.m
// keyBoard
#import "KBJfPay.h"
#import "KBJfPayCell.h"
#import "FGIAPProductsFilter.h"
#import "FGIAPManager.h"
#import "PayVM.h"
#import "KBPayProductModel.h"
#import "KBBizCode.h"
static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
@interface KBJfPay () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@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<KBPayProductModel *> *data; // In-App 商品展示数据
@property (nonatomic, assign) NSInteger selectedIndex; // 当前选中项
@property (nonatomic, strong) FGIAPProductsFilter *filter;
@property (nonatomic, strong) PayVM *payVM;
@end
@implementation KBJfPay
- (void)viewDidLoad {
[super viewDidLoad];
self.filter = [[FGIAPProductsFilter alloc] init];
self.payVM = [PayVM new];
self.data = @[];
self.selectedIndex = NSNotFound;
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 = KBLocalized(@"Points Recharge");
// 视图组装
[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(0);
}];
// 底部按钮
[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];
[self fetchInAppProductList];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self selectItemAtCurrentIndexAnimated: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 = (self.selectedIndex != NSNotFound && 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];
KBPayProductModel *model = self.data[indexPath.item];
NSString *coins = [model coinsDisplayText];
NSString *price = [model priceDisplayText];
[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 - Data
- (void)fetchInAppProductList {
__weak typeof(self) weakSelf = self;
[self.payVM fetchInAppProductsNeedShow:YES completion:^(NSInteger sta, NSString * _Nullable msg, NSArray<KBPayProductModel *> * _Nullable products) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) { return; }
if (sta != KBBizCodeSuccess || ![products isKindOfClass:NSArray.class]) {
self.data = @[];
self.selectedIndex = NSNotFound;
[self.collectionView reloadData];
NSString *tip = msg.length ? msg : KBLocalized(@"Failed to load products");
[KBHUD showInfo:tip];
return;
}
self.data = products ?: @[];
self.selectedIndex = self.data.count > 0 ? 0 : NSNotFound;
[self.collectionView reloadData];
[self selectItemAtCurrentIndexAnimated:NO];
});
}];
}
- (KBPayProductModel *)currentSelectedProductItem {
if (self.selectedIndex == NSNotFound) { return nil; }
if (self.selectedIndex < 0 || self.selectedIndex >= self.data.count) { return nil; }
id item = self.data[self.selectedIndex];
if (![item isKindOfClass:KBPayProductModel.class]) { return nil; }
return item;
}
- (void)selectItemAtCurrentIndexAnimated:(BOOL)animated {
if (self.selectedIndex == NSNotFound) { return; }
if (self.selectedIndex < 0 || self.selectedIndex >= self.data.count) { return; }
NSIndexPath *ip = [NSIndexPath indexPathForItem:self.selectedIndex inSection:0];
if (!ip) { return; }
dispatch_async(dispatch_get_main_queue(), ^{
if (self.selectedIndex == NSNotFound) { return; }
if ([self.collectionView numberOfItemsInSection:0] <= ip.item) { return; }
[self.collectionView selectItemAtIndexPath:ip animated:animated scrollPosition:UICollectionViewScrollPositionNone];
KBJfPayCell *cell = (KBJfPayCell *)[self.collectionView cellForItemAtIndexPath:ip];
if ([cell isKindOfClass:KBJfPayCell.class]) {
[cell applySelected:YES animated:animated];
}
});
}
#pragma mark - Actions
- (void)onTapPayButton {
KBPayProductModel *selectedItem = [self currentSelectedProductItem];
if (!selectedItem) {
[KBHUD showInfo:KBLocalized(@"Please select a product")];
return;
}
NSString *productId = selectedItem.productId;
if (productId.length == 0) {
[KBHUD showInfo:KBLocalized(@"Product unavailable")];
return;
}
[KBHUD show];
__weak typeof(self) weakSelf = self;
[self.filter requestProductsWith:[NSSet setWithObject:productId] completion:^(NSArray<SKProduct *> * _Nonnull products) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) { return; }
SKProduct *match = nil;
for (SKProduct *product in products) {
if ([product.productIdentifier isEqualToString:productId]) {
match = product;
break;
}
}
if (!match) {
[KBHUD dismiss];
[KBHUD showInfo:KBLocalized(@"Unable to load product information")];
return;
}
[[FGIAPManager shared].iap buyProduct:match onCompletion:^(NSString * _Nonnull message, FGIAPManagerPurchaseRusult result) {
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD dismiss];
});
}];
});
}];
}
- (void)agreementButtonAction{
[KBHUD showInfo:KBLocalized(@"Open agreement")];
}
#pragma mark - Lazy UI
- (UILabel *)myPointsTitleLabel {
if (!_myPointsTitleLabel) {
_myPointsTitleLabel = [UILabel new];
_myPointsTitleLabel.text = KBLocalized(@"My Points");
_myPointsTitleLabel.font = [KBFont medium:14];
_myPointsTitleLabel.textColor = [UIColor colorWithHex:KBBlackValue];
}
return _myPointsTitleLabel;
}
- (UILabel *)pointsLabel {
if (!_pointsLabel) {
_pointsLabel = [UILabel new];
_pointsLabel.text = @"4230"; // 示例值
_pointsLabel.font = [KBFont bold:30];
_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 = KBLocalized(@"Recharge Now");
// _rechargeLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
_rechargeLabel.font = [KBFont medium:14];
_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:KBLocalized(@"Recharge Now") forState:UIControlStateNormal];
[_payButton setTitleColor:[UIColor colorWithHex:KBBlackValue] forState:UIControlStateNormal];
// _payButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
_payButton.titleLabel.font = [KBFont medium:15];
// 使用现有的切图(若不存在可退化为渐变图片)
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 = KBLocalized(@"By clicking Pay, you indicate your agreement to the"); // 简化文案
// _agreementLabel.font = [UIFont systemFontOfSize:11 weight:UIFontWeightRegular];
_agreementLabel.font = [KBFont regular:12];
_agreementLabel.textColor = [UIColor colorWithWhite:0.45 alpha:1.0];
}
return _agreementLabel;
}
- (UIButton *)agreementButton {
if (!_agreementButton) {
_agreementButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_agreementButton setTitle:KBLocalized(@"《Embership Agreement》") forState:UIControlStateNormal];
[_agreementButton setTitleColor:[UIColor colorWithHex:KBColorValue] forState:UIControlStateNormal];
// _agreementButton.titleLabel.font = [UIFont systemFontOfSize:10 weight:UIFontWeightSemibold];
_agreementButton.titleLabel.font = [KBFont regular:12];
[_agreementButton addTarget:self action:@selector(agreementButtonAction) forControlEvents:UIControlEventTouchUpInside];
}
return _agreementButton;
}
@end