Files
keyboard/keyBoard/Class/Pay/VC/KBPaySvipVC.m
2026-02-04 15:57:22 +08:00

386 lines
16 KiB
Objective-C
Raw Permalink 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.

//
// KBPaySvipVC.m
// keyBoard
//
// Created by Mac on 2026/2/3.
//
#import "KBPaySvipVC.h"
#import "KBSvipSubscribeCell.h"
#import "KBSvipBenefitCell.h"
#import "KBSvipFlowLayout.h"
#import "PayVM.h"
#import "KBPayProductModel.h"
#import "KBBizCode.h"
#import "keyBoard-Swift.h"
#import <MJExtension/MJExtension.h>
static NSString * const kKBSvipSubscribeCellId = @"kKBSvipSubscribeCellId";
static NSString * const kKBSvipBenefitCellId = @"kKBSvipBenefitCellId";
static NSString * const kKBSvipBenefitHeaderId = @"kKBSvipBenefitHeaderId";
@interface KBPaySvipVC () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, copy) void(^scrollCallback)(UIScrollView *scrollView);
/// 1UI 控件
@property (nonatomic, strong) UICollectionView *collectionView;
/// 2数据
@property (nonatomic, strong) NSArray<KBPayProductModel *> *plans;
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, strong) NSArray<NSDictionary *> *benefits;
@property (nonatomic, strong) PayVM *payVM;
@end
@implementation KBPaySvipVC
#pragma mark - Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
/// 1控件初始化
[self setupUI];
/// 2数据初始化
[self setupData];
/// 3加载数据
[self loadData];
}
#pragma mark - 1控件初始化
- (void)setupUI {
self.view.backgroundColor = [UIColor colorWithHex:0xF6F7FB];
[self.view addSubview:self.collectionView];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
}
#pragma mark - 2数据初始化
- (void)setupData {
self.payVM = [PayVM new];
self.plans = @[];
self.selectedIndex = NSNotFound;
// 权益列表数据(使用现有图标资源)
self.benefits = @[
@{@"icon": @"pay_ais_icon", @"title": KBLocalized(@"Wireless Sub-ai Dialogue")},
@{@"icon": @"pay_keyboards_icon", @"title": KBLocalized(@"Personalized Keyboard")},
@{@"icon": @"pay_person_icon", @"title": KBLocalized(@"Chat Persona")},
@{@"icon": @"pay_phone_icon", @"title": KBLocalized(@"Emotional Counseling")},
@{@"icon": @"pay_history_icon", @"title": KBLocalized(@"Longer Chat History")},
@{@"icon": @"pay_chats_icon", @"title": KBLocalized(@"Unlimited Chatting")},
@{@"icon": @"pay_speed_icon", @"title": KBLocalized(@"Chat Without Speed Limits")},
@{@"icon": @"pay_soon_icon", @"title": KBLocalized(@"Coming Soon")},
];
}
#pragma mark - 3加载数据
- (void)loadData {
__weak typeof(self) weakSelf = self;
[self.payVM fetchSubscriptionProductsNeedShow: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.plans = @[];
self.selectedIndex = NSNotFound;
[self.collectionView reloadData];
return;
}
// 过滤 level=2 的 SVIP 数据
NSMutableArray<KBPayProductModel *> *svipProducts = [NSMutableArray array];
for (KBPayProductModel *product in products) {
if (product.level == 1) {
[svipProducts addObject:product];
}
}
self.plans = svipProducts.copy;
self.selectedIndex = self.plans.count > 1 ? 1 : (self.plans.count > 0 ? 0 : NSNotFound); // 默认选中第二个1 Month
[self.collectionView reloadData];
[self selectCurrentPlanAnimated:NO];
[self prepareStoreKitWithPlans:self.plans];
});
}];
}
#pragma mark - Private Methods
- (void)prepareStoreKitWithPlans:(NSArray<KBPayProductModel *> *)plans {
if (![plans isKindOfClass:NSArray.class] || plans.count == 0) { return; }
NSMutableArray<NSString *> *ids = [NSMutableArray array];
for (KBPayProductModel *plan in plans) {
if (![plan isKindOfClass:KBPayProductModel.class]) { continue; }
if (plan.productId.length) {
[ids addObject:plan.productId];
}
}
if (ids.count == 0) { return; }
[[KBStoreKitBridge shared] prepareWithProductIds:ids completion:nil];
}
- (void)selectCurrentPlanAnimated:(BOOL)animated {
if (self.selectedIndex == NSNotFound) { return; }
if (self.selectedIndex < 0 || self.selectedIndex >= self.plans.count) { return; }
NSIndexPath *ip = [NSIndexPath indexPathForItem:self.selectedIndex inSection:0];
[self.collectionView selectItemAtIndexPath:ip animated:animated scrollPosition:UICollectionViewScrollPositionNone];
KBSvipSubscribeCell *cell = (KBSvipSubscribeCell *)[self.collectionView cellForItemAtIndexPath:ip];
if ([cell isKindOfClass:KBSvipSubscribeCell.class]) {
[cell applySelected:YES animated:animated];
}
}
- (KBPayProductModel *)currentSelectedPlan {
if (self.selectedIndex == NSNotFound) { return nil; }
if (self.selectedIndex < 0 || self.selectedIndex >= self.plans.count) { return nil; }
return self.plans[self.selectedIndex];
}
- (NSString *)displayTitleForPlan:(KBPayProductModel *)plan {
if (!plan) { return @""; }
if (plan.productDescription.length) { return plan.productDescription; }
NSString *name = plan.name ?: @"";
NSString *unit = plan.unit ?: @"";
if (name.length && unit.length) { return [NSString stringWithFormat:@"%@ %@", name, unit]; }
if (name.length) { return name; }
if (unit.length) { return unit; }
return KBLocalized(@"Subscription");
}
#pragma mark - Actions
- (void)onTapPayButton {
KBPayProductModel *plan = [self currentSelectedPlan];
if (!plan) {
[KBHUD showInfo:KBLocalized(@"Please select a product")];
return;
}
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([plan.productId isKindOfClass:NSString.class] && plan.productId.length > 0) {
extra[@"product_id"] = plan.productId;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_svip_pay_btn"
pageId:@"svip_pay"
elementId:@"pay_btn"
extra:extra.copy
completion:nil];
NSString *productId = plan.productId;
if (productId.length == 0) {
[KBHUD showInfo:KBLocalized(@"Product unavailable")];
return;
}
[KBHUD show];
__weak typeof(self) weakSelf = self;
[[KBStoreKitBridge shared] purchaseWithProductId:productId completion:^(BOOL success, NSString * _Nullable message) {
__strong typeof(weakSelf) self = weakSelf;
[KBHUD dismiss];
[KBHUD showInfo:KBLocalized(message)];
if (!self || !success) { return; }
[self selectCurrentPlanAnimated:NO];
}];
}
#pragma mark - UICollectionView DataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
// Section 0: 订阅选项(横向)
// Section 1: 权益列表
return 2;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
if (section == 0) {
return self.plans.count;
}
return self.benefits.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
KBSvipSubscribeCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBSvipSubscribeCellId forIndexPath:indexPath];
if (indexPath.item < self.plans.count) {
KBPayProductModel *plan = self.plans[indexPath.item];
NSString *title = [self displayTitleForPlan:plan];
NSString *currency = plan.currency ?: @"$";
NSString *price = [plan priceNumberText];
[cell configTitle:title currency:currency price:price strike:nil];
[cell applySelected:(indexPath.item == self.selectedIndex) animated:NO];
}
return cell;
} else {
KBSvipBenefitCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBSvipBenefitCellId forIndexPath:indexPath];
if (indexPath.item < self.benefits.count) {
NSDictionary *benefit = self.benefits[indexPath.item];
[cell configWithIcon:benefit[@"icon"] title:benefit[@"title"]];
}
return cell;
}
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 1 && [kind isEqualToString:UICollectionElementKindSectionHeader]) {
UICollectionReusableView *header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kKBSvipBenefitHeaderId forIndexPath:indexPath];
// 清除旧的子视图
for (UIView *subview in header.subviews) {
[subview removeFromSuperview];
}
// 添加标题
UILabel *titleLabel = [UILabel new];
titleLabel.text = KBLocalized(@"Membership Benefits");
titleLabel.textColor = [UIColor colorWithHex:0x999999];
titleLabel.font = [KBFont medium:13];
titleLabel.textAlignment = NSTextAlignmentCenter;
[header addSubview:titleLabel];
[titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(header);
}];
// 左右横线
UIImageView *leftLine = [UIImageView new];
leftLine.image = [UIImage imageNamed:@"pay_leftline_icon"];
[header addSubview:leftLine];
[leftLine mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(titleLabel.mas_left).offset(-12);
make.centerY.equalTo(header);
make.width.mas_equalTo(53);
make.height.mas_equalTo(1);
}];
UIImageView *rightLine = [UIImageView new];
rightLine.image = [UIImage imageNamed:@"pay_rightline_icon"];
[header addSubview:rightLine];
[rightLine mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(titleLabel.mas_right).offset(12);
make.centerY.equalTo(header);
make.width.mas_equalTo(53);
make.height.mas_equalTo(1);
}];
return header;
}
return [UICollectionReusableView new];
}
#pragma mark - UICollectionView Delegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section != 0 || indexPath.item >= self.plans.count) { return; }
if (self.selectedIndex == indexPath.item) { return; }
NSInteger old = self.selectedIndex;
self.selectedIndex = indexPath.item;
KBPayProductModel *plan = self.plans[indexPath.item];
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
extra[@"index"] = @(indexPath.item);
if ([plan.productId isKindOfClass:NSString.class] && plan.productId.length > 0) {
extra[@"product_id"] = plan.productId;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_svip_select_plan"
pageId:@"svip_pay"
elementId:@"plan_item"
extra:extra.copy
completion:nil];
KBSvipSubscribeCell *newCell = (KBSvipSubscribeCell *)[collectionView cellForItemAtIndexPath:indexPath];
[newCell applySelected:YES animated:YES];
if (old >= 0 && old < self.plans.count) {
NSIndexPath *oldIP = [NSIndexPath indexPathForItem:old inSection:0];
KBSvipSubscribeCell *oldCell = (KBSvipSubscribeCell *)[collectionView cellForItemAtIndexPath:oldIP];
[oldCell applySelected:NO animated:YES];
}
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0 && indexPath.item < self.plans.count && [cell isKindOfClass:KBSvipSubscribeCell.class]) {
BOOL sel = (indexPath.item == self.selectedIndex);
KBSvipSubscribeCell *c = (KBSvipSubscribeCell *)cell;
if (sel) {
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
[c applySelected:sel animated:NO];
}
}
#pragma mark - FlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat screenW = KB_SCREEN_WIDTH;
if (indexPath.section == 0) {
// 订阅选项:尺寸 105 * 124
return CGSizeMake(105, 124);
} else {
// 权益项
return CGSizeMake(screenW - 32, 56);
}
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
if (section == 1) {
return CGSizeMake(KB_SCREEN_WIDTH, 50);
}
return CGSizeZero;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
if (section == 0) { return 10; }
return 0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
if (section == 0) { return 10; }
return 0;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
if (section == 0) {
// 订阅选项外层背景距屏幕16Cell距外层背景17所以 16+17=33
return UIEdgeInsetsMake(16, 22, 10, 22);
}
return UIEdgeInsetsMake(0, 16, 20, 16);
}
#pragma mark - UIScrollView Delegate转发给分页容器
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
!self.scrollCallback ?: self.scrollCallback(scrollView);
}
#pragma mark - JXPagingViewListViewDelegate
- (UIView *)listView { return self.view; }
- (UIScrollView *)listScrollView { return self.collectionView; }
- (void)listViewDidScrollCallback:(void (^)(UIScrollView *))callback { self.scrollCallback = callback; }
- (void)listWillAppear { NSLog(@"%@:%@", self.title, NSStringFromSelector(_cmd)); }
- (void)listDidAppear { NSLog(@"%@:%@", self.title, NSStringFromSelector(_cmd)); }
- (void)listWillDisappear { NSLog(@"%@:%@", self.title, NSStringFromSelector(_cmd)); }
- (void)listDidDisappear { NSLog(@"%@:%@", self.title, NSStringFromSelector(_cmd)); }
#pragma mark - Lazy
- (UICollectionView *)collectionView {
if (!_collectionView) {
KBSvipFlowLayout *layout = [KBSvipFlowLayout new];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.decorationSection = 1; // Section 1 添加背景
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.backgroundColor = [UIColor clearColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.alwaysBounceVertical = YES;
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
[_collectionView registerClass:KBSvipSubscribeCell.class forCellWithReuseIdentifier:kKBSvipSubscribeCellId];
[_collectionView registerClass:KBSvipBenefitCell.class forCellWithReuseIdentifier:kKBSvipBenefitCellId];
[_collectionView registerClass:UICollectionReusableView.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kKBSvipBenefitHeaderId];
}
return _collectionView;
}
@end