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

442 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.

//
// 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;
@property (nonatomic, strong) UIButton *payButton;
@property (nonatomic, strong) UILabel *agreementLabel;
@property (nonatomic, strong) UIButton *agreementButton;
/// 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.payButton];
[self.view addSubview:self.agreementLabel];
[self.view addSubview:self.agreementButton];
[self.view addSubview:self.collectionView];
[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 mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view);
make.top.equalTo(self.view).offset(16);
make.bottom.equalTo(self.payButton.mas_top).offset(-16);
}];
}
#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;
}
self.plans = products ?: @[];
self.selectedIndex = self.plans.count > 0 ? 1 : 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];
}];
}
- (void)agreementButtonAction {
[KBHUD showInfo:KBLocalized(@"Open agreement")];
}
#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 *price = [plan priceDisplayText];
[cell configTitle:title 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);
}];
// 左右横线
UIView *leftLine = [UIView new];
leftLine.backgroundColor = [UIColor colorWithHex:0xE5E5E5];
[header addSubview:leftLine];
[leftLine mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(titleLabel.mas_left).offset(-12);
make.centerY.equalTo(header);
make.width.mas_equalTo(40);
make.height.mas_equalTo(1);
}];
UIView *rightLine = [UIView new];
rightLine.backgroundColor = [UIColor colorWithHex:0xE5E5E5];
[header addSubview:rightLine];
[rightLine mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(titleLabel.mas_right).offset(12);
make.centerY.equalTo(header);
make.width.mas_equalTo(40);
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) {
return UIEdgeInsetsMake(16, 16, 10, 16);
}
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;
}
- (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 = [KBFont medium:15];
_payButton.layer.cornerRadius = 29;
_payButton.clipsToBounds = YES;
_payButton.backgroundColor = [UIColor colorWithHex:0x222222];
[_payButton setTitleColor:[UIColor whiteColor] 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 = [KBFont regular:12];
_agreementLabel.textColor = [UIColor colorWithHex:KBBlackValue];
}
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 = [KBFont regular:12];
[_agreementButton addTarget:self action:@selector(agreementButtonAction) forControlEvents:UIControlEventTouchUpInside];
}
return _agreementButton;
}
@end