From 886de394d0277101c53461b6242b3846201dc443 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Wed, 17 Dec 2025 19:45:39 +0800 Subject: [PATCH] 1 --- .../buy_sel_icon.imageset/Contents.json | 22 +++++++ .../buy_sel_icon.imageset/buy_sel_icon@2x.png | Bin 0 -> 1902 bytes .../buy_sel_icon.imageset/buy_sel_icon@3x.png | Bin 0 -> 3572 bytes CustomKeyboard/KeyboardViewController.m | 2 +- .../View/KBKeyboardSubscriptionOptionCell.m | 50 ++++++---------- .../View/KBKeyboardSubscriptionView.m | 54 ++++++++++++++++- Shared/KBConfig.h | 3 + keyBoard/AppDelegate.m | 50 +++++++++++++++- keyBoard/Class/Pay/VC/KBVipPay.h | 6 ++ keyBoard/Class/Pay/VC/KBVipPay.m | 56 +++++++++++++++++- 10 files changed, 207 insertions(+), 36 deletions(-) create mode 100644 CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/Contents.json create mode 100644 CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@2x.png create mode 100644 CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@3x.png diff --git a/CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/Contents.json b/CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/Contents.json new file mode 100644 index 0000000..dde21ca --- /dev/null +++ b/CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "buy_sel_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "buy_sel_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@2x.png b/CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..15d841606bfb37dba9bc529412331dd70719a15f GIT binary patch literal 1902 zcmV-!2a))RP)N^2JZU^ye0%CUIWgi~-W zn&tcZPwJ{&of;?>d3}3|xxVo9BPuV)a#$UUrkns7dDnj5n$>$Ads{|suJJfLG^o{k zx+|)6#7sxDvIO6zr*Q9~9^HTR|A_BQ*>E%e4UkVYyM`FUoQ*lUBjlsL8)$3nvmunkXJMRM3#+Z z&=pT^Pe(bDT*TKLG`zVk!533W!VRGpsJSNRrfO(M6L`c22m9qyVuSCU*Q#GG`he;) zS=}=K0^L0SJyxe%?y8-gdWNN}2~k-_Z(nnmOFRL%RAM0?bAL*prFL31cnBfMcFZ6Z zN0p|vqkKtSefGI}{DW8O>n#i6kUIH1ujJctm}T_rU7qe-3>|X+XWm+bqX)z=F*f%W$Tv21sil+1#u%g@gzgb$*e6|XV3R?yn(unMwBZ*pO z!3j)fbUxpIIsB5=@X`7+mscTMBcqYleYVMyfPHfKdJr)&25QMM981zLxG#nf_9JYu z)2i8XlTi$+9=95Td*mL~bVk=epcxql&1ojb&hYWG&V9DAt$pm94Iy#Gpv1jq&ipiE zk{%4ijtDM4c6I&S^EEq{$1s#;0LcWkB$1C%o?ZuNV7eQ@j4~`+=RVumHiwUWBOFGc zb+R{bY*l)0a}!;GZD6y5-?{L8m`Wi?&f3f1%~S(nK9ttCjwyDq>1?_Rtn^uQ`I0IL(cC3|s?JiK#ACFVL(3x|Pf2^PF|daLI^NHjc1G1u_riwGBy5 zy3{8{Y$-CtV=i+dx_pi507xaKdaT-nc)O>3J-40rgh%0vsL1bs|4MW_qEQvW@Mw|$ z2E`;N-8dUW+1DKAGAG>rqqC8=S|%sp4*!w30b%%?EX9WRUaTec1r5VEL?(j?MfDd) z()XjEAFq}NX$R>WqP6V;z8@PGx62%G^@AJ1kHCbI5(pu1*)k+m@pG&`23~wW!GGP_ zCU4GVGn$xQsgK}&BO~hL%U1$2x)`KoGn)0o1&ehziMLK~ghKv{v=ZSiP>oZLsgwX6 za&5>p6+-IAMOIPg){B8&4 z=%h8Lxe>?8=@PKv2xca^gZ5Hz8`zNvVW1^nr#5~cTw0jLh-W3)I`?7o1Xv5~^9$OO zz+C1ucpWQ~6R<%A&e)Frd*m)iK62)quhdgFZdJi_ryB)*~Yf^v9CGIRSi5o)@uL& o0RR8WEXD-@000I_L_t&o0EG;3bDGz@G5`Po07*qoM6N<$f;Z={;Q#;t literal 0 HcmV?d00001 diff --git a/CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@3x.png b/CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e21756ecc5a7d5ec4e1cd95590502aced6be6a2d GIT binary patch literal 3572 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 *params = [NSMutableArray arrayWithObject:@"autoPay=1"]; + NSMutableArray *params = [NSMutableArray arrayWithObjects:@"autoPay=1", @"prefill=1", nil]; if (encodedId.length) { [params addObject:[NSString stringWithFormat:@"productId=%@", encodedId]]; } diff --git a/CustomKeyboard/View/KBKeyboardSubscriptionOptionCell.m b/CustomKeyboard/View/KBKeyboardSubscriptionOptionCell.m index f205121..c16116a 100644 --- a/CustomKeyboard/View/KBKeyboardSubscriptionOptionCell.m +++ b/CustomKeyboard/View/KBKeyboardSubscriptionOptionCell.m @@ -11,8 +11,7 @@ @property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UILabel *priceLabel; @property (nonatomic, strong) UILabel *strikeLabel; -@property (nonatomic, strong) UIView *checkBadge; -@property (nonatomic, strong) UILabel *checkLabel; +@property (nonatomic, strong) UIImageView *selectedImageView; @end @implementation KBKeyboardSubscriptionOptionCell - (instancetype)initWithFrame:(CGRect)frame { @@ -22,8 +21,7 @@ [self.cardView addSubview:self.titleLabel]; [self.cardView addSubview:self.priceLabel]; [self.cardView addSubview:self.strikeLabel]; - [self.cardView addSubview:self.checkBadge]; - [self.checkBadge addSubview:self.checkLabel]; + [self.cardView addSubview:self.selectedImageView]; [self.cardView mas_makeConstraints:^(MASConstraintMaker *make) { // make.edges.equalTo(self.contentView); @@ -32,7 +30,7 @@ }]; [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(self.cardView.mas_top).offset(4); + make.top.equalTo(self.cardView.mas_top).offset(8); make.left.equalTo(self.cardView.mas_left).offset(10); make.right.equalTo(self.cardView.mas_right).offset(-10); }]; @@ -47,14 +45,11 @@ make.centerY.equalTo(self.priceLabel); }]; - [self.checkBadge mas_makeConstraints:^(MASConstraintMaker *make) { + [self.selectedImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self.cardView.mas_centerX); make.bottom.equalTo(self.cardView.mas_bottom).offset(10); - make.width.height.mas_equalTo(20); - }]; - - [self.checkLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.center.equalTo(self.checkBadge); + make.width.mas_equalTo(16); + make.height.mas_equalTo(17); }]; } return self; @@ -92,13 +87,16 @@ void (^changes)(void) = ^{ self.cardView.layer.borderColor = selected ? [UIColor colorWithHex:0x02BEAC].CGColor : [[UIColor blackColor] colorWithAlphaComponent:0.12].CGColor; self.cardView.layer.borderWidth = selected ? 2.0 : 1.0; - self.checkBadge.backgroundColor = selected ? [UIColor colorWithHex:0x02BEAC] : [[UIColor blackColor] colorWithAlphaComponent:0.15]; - self.checkLabel.textColor = [UIColor whiteColor]; + self.selectedImageView.alpha = selected ? 1.0 : 0.0; }; if (animated) { - [UIView animateWithDuration:0.18 animations:changes]; + self.selectedImageView.hidden = NO; + [UIView animateWithDuration:0.18 animations:changes completion:^(BOOL finished) { + self.selectedImageView.hidden = !selected; + }]; } else { changes(); + self.selectedImageView.hidden = !selected; } } @@ -141,23 +139,13 @@ return _strikeLabel; } -- (UIView *)checkBadge { - if (!_checkBadge) { - _checkBadge = [[UIView alloc] init]; - _checkBadge.layer.cornerRadius = 10; - _checkBadge.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.15]; +- (UIImageView *)selectedImageView { + if (!_selectedImageView) { + _selectedImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"buy_sel_icon"]]; + _selectedImageView.contentMode = UIViewContentModeScaleAspectFit; + _selectedImageView.hidden = YES; + _selectedImageView.alpha = 0.0; } - return _checkBadge; -} - -- (UILabel *)checkLabel { - if (!_checkLabel) { - _checkLabel = [[UILabel alloc] init]; - _checkLabel.text = @"✓"; - _checkLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightBold]; - _checkLabel.textColor = [UIColor whiteColor]; - _checkLabel.textAlignment = NSTextAlignmentCenter; - } - return _checkLabel; + return _selectedImageView; } @end diff --git a/CustomKeyboard/View/KBKeyboardSubscriptionView.m b/CustomKeyboard/View/KBKeyboardSubscriptionView.m index cd877f1..d8add92 100644 --- a/CustomKeyboard/View/KBKeyboardSubscriptionView.m +++ b/CustomKeyboard/View/KBKeyboardSubscriptionView.m @@ -9,11 +9,39 @@ #import "KBFullAccessManager.h" #import "KBKeyboardSubscriptionFeatureMarqueeView.h" #import "KBKeyboardSubscriptionOptionCell.h" +#import "KBConfig.h" #import static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptionCellId"; +static id KBKeyboardSubscriptionSanitizeJSON(id obj) { + if (!obj || obj == (id)kCFNull) { return nil; } + if ([obj isKindOfClass:[NSDictionary class]]) { + NSDictionary *dict = (NSDictionary *)obj; + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:dict.count]; + [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + (void)stop; + if (![key isKindOfClass:[NSString class]]) { return; } + id sanitized = KBKeyboardSubscriptionSanitizeJSON(value); + if (!sanitized) { return; } + result[key] = sanitized; + }]; + return result; + } + if ([obj isKindOfClass:[NSArray class]]) { + NSArray *arr = (NSArray *)obj; + NSMutableArray *result = [NSMutableArray arrayWithCapacity:arr.count]; + for (id item in arr) { + id sanitized = KBKeyboardSubscriptionSanitizeJSON(item); + if (!sanitized) { continue; } + [result addObject:sanitized]; + } + return result; + } + return obj; +} + @interface KBKeyboardSubscriptionView () @property (nonatomic, strong) UIImageView *cardView; @property (nonatomic, strong) UIButton *closeButton; @@ -25,6 +53,7 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio @property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator; @property (nonatomic, strong) UILabel *emptyLabel; @property (nonatomic, copy) NSArray *products; +@property (nonatomic, copy, nullable) NSArray *productsRawJSON; @property (nonatomic, assign) NSInteger selectedIndex; @property (nonatomic, assign) BOOL didLoadOnce; @property (nonatomic, assign, getter=isLoading) BOOL loading; @@ -156,6 +185,7 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio return; } KBKeyboardSubscriptionProduct *product = self.products[self.selectedIndex]; + [self kb_persistPrefillPayloadForProduct:product]; if ([self.delegate respondsToSelector:@selector(subscriptionView:didTapPurchaseForProduct:)]) { [self.delegate subscriptionView:self didTapPurchaseForProduct:product]; } @@ -191,6 +221,7 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio NSString *tip = error.localizedDescription ?: KBLocalized(@"Network error"); [KBHUD showInfo:tip]; self.products = @[]; + self.productsRawJSON = nil; self.selectedIndex = NSNotFound; [self.collectionView reloadData]; self.emptyLabel.hidden = NO; @@ -203,12 +234,15 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio } if (![dataObj isKindOfClass:[NSArray class]]) { self.products = @[]; + self.productsRawJSON = nil; self.selectedIndex = NSNotFound; [self.collectionView reloadData]; self.emptyLabel.hidden = NO; [self updatePurchaseButtonState]; return; } + id sanitized = KBKeyboardSubscriptionSanitizeJSON(dataObj); + self.productsRawJSON = [sanitized isKindOfClass:NSArray.class] ? (NSArray *)sanitized : nil; NSArray *models = [KBKeyboardSubscriptionProduct mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj]; self.products = models ?: @[]; self.selectedIndex = self.products.count > 0 ? 0 : NSNotFound; @@ -221,6 +255,25 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio }]; } +- (void)kb_persistPrefillPayloadForProduct:(KBKeyboardSubscriptionProduct *)product { + if (![product isKindOfClass:KBKeyboardSubscriptionProduct.class]) { return; } + if (![self.productsRawJSON isKindOfClass:NSArray.class] || self.productsRawJSON.count == 0) { return; } + NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; + if (!ud) { return; } + NSMutableDictionary *payload = [NSMutableDictionary dictionary]; + payload[@"ts"] = @((long long)floor([NSDate date].timeIntervalSince1970)); + payload[@"src"] = @"keyboard"; + if (product.productId.length) { + payload[@"productId"] = product.productId; + } + if (self.selectedIndex != NSNotFound) { + payload[@"selectedIndex"] = @(self.selectedIndex); + } + payload[@"products"] = self.productsRawJSON; + [ud setObject:payload forKey:AppGroup_SubscriptionPrefillPayload]; + [ud synchronize]; +} + - (void)selectCurrentProductAnimated:(BOOL)animated { if (self.selectedIndex == NSNotFound || self.selectedIndex >= self.products.count) { return; } NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.selectedIndex inSection:0]; @@ -401,4 +454,3 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio } @end - diff --git a/Shared/KBConfig.h b/Shared/KBConfig.h index 63c85d4..79ab795 100644 --- a/Shared/KBConfig.h +++ b/Shared/KBConfig.h @@ -24,6 +24,9 @@ /// 键盘JSON数据 #define AppGroup_MyKbJson @"AppGroup_MyKbJson" +/// 键盘 -> 主 App 订阅页预填充数据(用于免二次请求) +#define AppGroup_SubscriptionPrefillPayload @"AppGroup_SubscriptionPrefillPayload" + /// 皮肤图标加载模式: /// 0 = 使用本地 Assets 图片名(key_icons 的 value 写成图片名,例如 "kb_q_melon") /// 1 = 使用远程 Zip 皮肤包(skinJSON 中提供 zip_url;key_icons 的 value 写成 Zip 内图标文件名,例如 "key_a") diff --git a/keyBoard/AppDelegate.m b/keyBoard/AppDelegate.m index 95abc4f..0af4a55 100644 --- a/keyBoard/AppDelegate.m +++ b/keyBoard/AppDelegate.m @@ -24,6 +24,9 @@ #import "KBVipPay.h" #import "KBUserSessionManager.h" #import "KBLoginVC.h" +#import "KBConfig.h" + +static NSTimeInterval const kKBSubscriptionPrefillTTL = 10 * 60.0; // 注意:用于判断系统已启用本输入法扩展的 bundle id 需与扩展 target 的 // PRODUCT_BUNDLE_IDENTIFIER 完全一致。 @@ -199,8 +202,36 @@ if ([action isEqualToString:@"autopay"]) { autoPay = YES; } + + BOOL wantsPrefill = NO; + NSString *prefillFlag = params[@"prefill"]; + if ([prefillFlag respondsToSelector:@selector(boolValue)] && prefillFlag.boolValue) { + wantsPrefill = YES; + } + NSString *src = params[@"src"]; + if ([src isKindOfClass:NSString.class] && [src.lowercaseString isEqualToString:@"keyboard"]) { + wantsPrefill = YES; + } + NSDictionary *prefillPayload = wantsPrefill ? [self kb_consumeSubscriptionPrefillPayloadIfValid] : nil; + if ([prefillPayload isKindOfClass:NSDictionary.class]) { + NSString *payloadProductId = prefillPayload[@"productId"]; + if (productId.length == 0 && [payloadProductId isKindOfClass:NSString.class]) { + productId = payloadProductId; + } + } + KBVipPay *vc = [[KBVipPay alloc] init]; - [vc configureWithProductId:productId autoPurchase:autoPay]; + if ([prefillPayload isKindOfClass:NSDictionary.class]) { + NSArray *productsJSON = prefillPayload[@"products"]; + NSNumber *selectedIndexNumber = prefillPayload[@"selectedIndex"]; + NSInteger selectedIndex = [selectedIndexNumber respondsToSelector:@selector(integerValue)] ? selectedIndexNumber.integerValue : NSNotFound; + [vc configureWithProductId:productId + autoPurchase:autoPay + prefillProductsJSON:productsJSON + selectedIndex:selectedIndex]; + } else { + [vc configureWithProductId:productId autoPurchase:autoPay]; + } [KB_CURRENT_NAV pushViewController:vc animated:true]; return YES; } @@ -209,6 +240,23 @@ return NO; } +- (nullable NSDictionary *)kb_consumeSubscriptionPrefillPayloadIfValid { + NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; + if (!ud) { return nil; } + id obj = [ud objectForKey:AppGroup_SubscriptionPrefillPayload]; + [ud removeObjectForKey:AppGroup_SubscriptionPrefillPayload]; + [ud synchronize]; + if (![obj isKindOfClass:NSDictionary.class]) { return nil; } + NSDictionary *payload = (NSDictionary *)obj; + NSNumber *ts = payload[@"ts"]; + if (![ts respondsToSelector:@selector(doubleValue)]) { return nil; } + NSTimeInterval age = [NSDate date].timeIntervalSince1970 - ts.doubleValue; + if (age < 0 || age > kKBSubscriptionPrefillTTL) { return nil; } + id products = payload[@"products"]; + if (![products isKindOfClass:NSArray.class] || ((NSArray *)products).count == 0) { return nil; } + return payload; +} + - (NSDictionary *)kb_queryParametersFromURL:(NSURL *)url { if (!url) { return @{}; } NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; diff --git a/keyBoard/Class/Pay/VC/KBVipPay.h b/keyBoard/Class/Pay/VC/KBVipPay.h index 5504032..c5d254e 100644 --- a/keyBoard/Class/Pay/VC/KBVipPay.h +++ b/keyBoard/Class/Pay/VC/KBVipPay.h @@ -15,6 +15,12 @@ NS_ASSUME_NONNULL_BEGIN /// 通过键盘深链配置初始商品及是否自动发起购买 - (void)configureWithProductId:(nullable NSString *)productId autoPurchase:(BOOL)autoPurchase; + +/// 通过键盘扩展预填充商品列表(免二次请求) +- (void)configureWithProductId:(nullable NSString *)productId + autoPurchase:(BOOL)autoPurchase + prefillProductsJSON:(nullable NSArray *)productsJSON + selectedIndex:(NSInteger)selectedIndex; @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Pay/VC/KBVipPay.m b/keyBoard/Class/Pay/VC/KBVipPay.m index e9eb266..b43f65c 100644 --- a/keyBoard/Class/Pay/VC/KBVipPay.m +++ b/keyBoard/Class/Pay/VC/KBVipPay.m @@ -13,6 +13,7 @@ #import "KBPayProductModel.h" #import "KBBizCode.h" #import "keyBoard-Swift.h" +#import static NSString * const kKBVipHeaderId = @"kKBVipHeaderId"; static NSString * const kKBVipSubscribeCellId = @"kKBVipSubscribeCellId"; @@ -37,6 +38,9 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; @property (nonatomic, assign) BOOL pendingAutoPurchase; @property (nonatomic, assign) BOOL hasTriggeredAutoPurchase; @property (nonatomic, assign) BOOL viewVisible; +@property (nonatomic, copy, nullable) NSArray *prefillProductsJSON; +@property (nonatomic, assign) NSInteger prefillSelectedIndex; +@property (nonatomic, assign) BOOL didApplyPrefillPlans; @end @@ -59,6 +63,7 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; self.payVM = [PayVM new]; self.plans = @[]; self.selectedIndex = NSNotFound; + self.prefillSelectedIndex = NSNotFound; // 组装主列表 [self.view addSubview:self.collectionView]; @@ -101,9 +106,12 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; // 预计算 Header 高度(由内部约束决定) self.headerHeight = [self kb_calcHeaderHeightForWidth:KB_SCREEN_WIDTH]; + BOOL appliedPrefill = [self kb_applyPrefillPlansIfPossible]; [self.collectionView reloadData]; - - [self fetchSubscriptionPlans]; + [self selectCurrentPlanAnimated:NO]; + if (!appliedPrefill) { + [self fetchSubscriptionPlans]; + } } - (void)viewDidAppear:(BOOL)animated { @@ -128,6 +136,26 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; [self kb_triggerAutoPurchaseIfNeeded]; } +- (void)configureWithProductId:(nullable NSString *)productId + autoPurchase:(BOOL)autoPurchase + prefillProductsJSON:(nullable NSArray *)productsJSON + selectedIndex:(NSInteger)selectedIndex { + self.pendingProductId = productId.length ? [productId copy] : nil; + self.pendingAutoPurchase = autoPurchase; + self.hasTriggeredAutoPurchase = NO; + self.prefillProductsJSON = [productsJSON isKindOfClass:NSArray.class] ? [productsJSON copy] : nil; + self.prefillSelectedIndex = selectedIndex; + self.didApplyPrefillPlans = NO; + if (self.isViewLoaded) { + BOOL ok = [self kb_applyPrefillPlansIfPossible]; + if (ok) { + [self.collectionView reloadData]; + [self selectCurrentPlanAnimated:NO]; + } + } + [self kb_triggerAutoPurchaseIfNeeded]; +} + #pragma mark - Data - (void)fetchSubscriptionPlans { __weak typeof(self) weakSelf = self; @@ -154,6 +182,30 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; }]; } +- (BOOL)kb_applyPrefillPlansIfPossible { + if (self.didApplyPrefillPlans) { + return (self.plans.count > 0); + } + if (![self.prefillProductsJSON isKindOfClass:NSArray.class] || self.prefillProductsJSON.count == 0) { + return NO; + } + NSArray *models = [KBPayProductModel mj_objectArrayWithKeyValuesArray:self.prefillProductsJSON]; + if (![models isKindOfClass:NSArray.class] || models.count == 0) { + return NO; + } + self.didApplyPrefillPlans = YES; + self.plans = models; + NSInteger idx = self.prefillSelectedIndex; + if (idx != NSNotFound && idx >= 0 && idx < (NSInteger)self.plans.count) { + self.selectedIndex = idx; + } else { + self.selectedIndex = (self.plans.count > 0) ? 0 : NSNotFound; + } + [self prepareStoreKitWithPlans:self.plans]; + [self kb_applyPendingPrefillIfNeeded]; + return YES; +} + - (void)prepareStoreKitWithPlans:(NSArray *)plans { if (![plans isKindOfClass:NSArray.class] || plans.count == 0) { return; } NSMutableArray *ids = [NSMutableArray array];