1
This commit is contained in:
22
CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/Contents.json
vendored
Normal file
22
CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/Contents.json
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@2x.png
vendored
Normal file
BIN
CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@3x.png
vendored
Normal file
BIN
CustomKeyboard/KeyboardAssets.xcassets/buy_sel_icon.imageset/buy_sel_icon@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@@ -359,7 +359,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
|||||||
NSString *encodedId = [self.class kb_urlEncodedString:product.productId];
|
NSString *encodedId = [self.class kb_urlEncodedString:product.productId];
|
||||||
NSString *title = [product displayTitle];
|
NSString *title = [product displayTitle];
|
||||||
NSString *encodedTitle = [self.class kb_urlEncodedString:title];
|
NSString *encodedTitle = [self.class kb_urlEncodedString:title];
|
||||||
NSMutableArray<NSString *> *params = [NSMutableArray arrayWithObject:@"autoPay=1"];
|
NSMutableArray<NSString *> *params = [NSMutableArray arrayWithObjects:@"autoPay=1", @"prefill=1", nil];
|
||||||
if (encodedId.length) {
|
if (encodedId.length) {
|
||||||
[params addObject:[NSString stringWithFormat:@"productId=%@", encodedId]];
|
[params addObject:[NSString stringWithFormat:@"productId=%@", encodedId]];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@
|
|||||||
@property (nonatomic, strong) UILabel *titleLabel;
|
@property (nonatomic, strong) UILabel *titleLabel;
|
||||||
@property (nonatomic, strong) UILabel *priceLabel;
|
@property (nonatomic, strong) UILabel *priceLabel;
|
||||||
@property (nonatomic, strong) UILabel *strikeLabel;
|
@property (nonatomic, strong) UILabel *strikeLabel;
|
||||||
@property (nonatomic, strong) UIView *checkBadge;
|
@property (nonatomic, strong) UIImageView *selectedImageView;
|
||||||
@property (nonatomic, strong) UILabel *checkLabel;
|
|
||||||
@end
|
@end
|
||||||
@implementation KBKeyboardSubscriptionOptionCell
|
@implementation KBKeyboardSubscriptionOptionCell
|
||||||
- (instancetype)initWithFrame:(CGRect)frame {
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||||||
@@ -22,8 +21,7 @@
|
|||||||
[self.cardView addSubview:self.titleLabel];
|
[self.cardView addSubview:self.titleLabel];
|
||||||
[self.cardView addSubview:self.priceLabel];
|
[self.cardView addSubview:self.priceLabel];
|
||||||
[self.cardView addSubview:self.strikeLabel];
|
[self.cardView addSubview:self.strikeLabel];
|
||||||
[self.cardView addSubview:self.checkBadge];
|
[self.cardView addSubview:self.selectedImageView];
|
||||||
[self.checkBadge addSubview:self.checkLabel];
|
|
||||||
|
|
||||||
[self.cardView mas_makeConstraints:^(MASConstraintMaker *make) {
|
[self.cardView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
// make.edges.equalTo(self.contentView);
|
// make.edges.equalTo(self.contentView);
|
||||||
@@ -32,7 +30,7 @@
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
[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.left.equalTo(self.cardView.mas_left).offset(10);
|
||||||
make.right.equalTo(self.cardView.mas_right).offset(-10);
|
make.right.equalTo(self.cardView.mas_right).offset(-10);
|
||||||
}];
|
}];
|
||||||
@@ -47,14 +45,11 @@
|
|||||||
make.centerY.equalTo(self.priceLabel);
|
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.centerX.equalTo(self.cardView.mas_centerX);
|
||||||
make.bottom.equalTo(self.cardView.mas_bottom).offset(10);
|
make.bottom.equalTo(self.cardView.mas_bottom).offset(10);
|
||||||
make.width.height.mas_equalTo(20);
|
make.width.mas_equalTo(16);
|
||||||
}];
|
make.height.mas_equalTo(17);
|
||||||
|
|
||||||
[self.checkLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
||||||
make.center.equalTo(self.checkBadge);
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
@@ -92,13 +87,16 @@
|
|||||||
void (^changes)(void) = ^{
|
void (^changes)(void) = ^{
|
||||||
self.cardView.layer.borderColor = selected ? [UIColor colorWithHex:0x02BEAC].CGColor : [[UIColor blackColor] colorWithAlphaComponent:0.12].CGColor;
|
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.cardView.layer.borderWidth = selected ? 2.0 : 1.0;
|
||||||
self.checkBadge.backgroundColor = selected ? [UIColor colorWithHex:0x02BEAC] : [[UIColor blackColor] colorWithAlphaComponent:0.15];
|
self.selectedImageView.alpha = selected ? 1.0 : 0.0;
|
||||||
self.checkLabel.textColor = [UIColor whiteColor];
|
|
||||||
};
|
};
|
||||||
if (animated) {
|
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 {
|
} else {
|
||||||
changes();
|
changes();
|
||||||
|
self.selectedImageView.hidden = !selected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,23 +139,13 @@
|
|||||||
return _strikeLabel;
|
return _strikeLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIView *)checkBadge {
|
- (UIImageView *)selectedImageView {
|
||||||
if (!_checkBadge) {
|
if (!_selectedImageView) {
|
||||||
_checkBadge = [[UIView alloc] init];
|
_selectedImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"buy_sel_icon"]];
|
||||||
_checkBadge.layer.cornerRadius = 10;
|
_selectedImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||||
_checkBadge.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.15];
|
_selectedImageView.hidden = YES;
|
||||||
|
_selectedImageView.alpha = 0.0;
|
||||||
}
|
}
|
||||||
return _checkBadge;
|
return _selectedImageView;
|
||||||
}
|
|
||||||
|
|
||||||
- (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;
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -9,11 +9,39 @@
|
|||||||
#import "KBFullAccessManager.h"
|
#import "KBFullAccessManager.h"
|
||||||
#import "KBKeyboardSubscriptionFeatureMarqueeView.h"
|
#import "KBKeyboardSubscriptionFeatureMarqueeView.h"
|
||||||
#import "KBKeyboardSubscriptionOptionCell.h"
|
#import "KBKeyboardSubscriptionOptionCell.h"
|
||||||
|
#import "KBConfig.h"
|
||||||
|
|
||||||
#import <MJExtension/MJExtension.h>
|
#import <MJExtension/MJExtension.h>
|
||||||
|
|
||||||
static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptionCellId";
|
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 () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
@interface KBKeyboardSubscriptionView () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||||
@property (nonatomic, strong) UIImageView *cardView;
|
@property (nonatomic, strong) UIImageView *cardView;
|
||||||
@property (nonatomic, strong) UIButton *closeButton;
|
@property (nonatomic, strong) UIButton *closeButton;
|
||||||
@@ -25,6 +53,7 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio
|
|||||||
@property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
|
@property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
|
||||||
@property (nonatomic, strong) UILabel *emptyLabel;
|
@property (nonatomic, strong) UILabel *emptyLabel;
|
||||||
@property (nonatomic, copy) NSArray<KBKeyboardSubscriptionProduct *> *products;
|
@property (nonatomic, copy) NSArray<KBKeyboardSubscriptionProduct *> *products;
|
||||||
|
@property (nonatomic, copy, nullable) NSArray *productsRawJSON;
|
||||||
@property (nonatomic, assign) NSInteger selectedIndex;
|
@property (nonatomic, assign) NSInteger selectedIndex;
|
||||||
@property (nonatomic, assign) BOOL didLoadOnce;
|
@property (nonatomic, assign) BOOL didLoadOnce;
|
||||||
@property (nonatomic, assign, getter=isLoading) BOOL loading;
|
@property (nonatomic, assign, getter=isLoading) BOOL loading;
|
||||||
@@ -156,6 +185,7 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
KBKeyboardSubscriptionProduct *product = self.products[self.selectedIndex];
|
KBKeyboardSubscriptionProduct *product = self.products[self.selectedIndex];
|
||||||
|
[self kb_persistPrefillPayloadForProduct:product];
|
||||||
if ([self.delegate respondsToSelector:@selector(subscriptionView:didTapPurchaseForProduct:)]) {
|
if ([self.delegate respondsToSelector:@selector(subscriptionView:didTapPurchaseForProduct:)]) {
|
||||||
[self.delegate subscriptionView:self didTapPurchaseForProduct:product];
|
[self.delegate subscriptionView:self didTapPurchaseForProduct:product];
|
||||||
}
|
}
|
||||||
@@ -191,6 +221,7 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio
|
|||||||
NSString *tip = error.localizedDescription ?: KBLocalized(@"Network error");
|
NSString *tip = error.localizedDescription ?: KBLocalized(@"Network error");
|
||||||
[KBHUD showInfo:tip];
|
[KBHUD showInfo:tip];
|
||||||
self.products = @[];
|
self.products = @[];
|
||||||
|
self.productsRawJSON = nil;
|
||||||
self.selectedIndex = NSNotFound;
|
self.selectedIndex = NSNotFound;
|
||||||
[self.collectionView reloadData];
|
[self.collectionView reloadData];
|
||||||
self.emptyLabel.hidden = NO;
|
self.emptyLabel.hidden = NO;
|
||||||
@@ -203,12 +234,15 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio
|
|||||||
}
|
}
|
||||||
if (![dataObj isKindOfClass:[NSArray class]]) {
|
if (![dataObj isKindOfClass:[NSArray class]]) {
|
||||||
self.products = @[];
|
self.products = @[];
|
||||||
|
self.productsRawJSON = nil;
|
||||||
self.selectedIndex = NSNotFound;
|
self.selectedIndex = NSNotFound;
|
||||||
[self.collectionView reloadData];
|
[self.collectionView reloadData];
|
||||||
self.emptyLabel.hidden = NO;
|
self.emptyLabel.hidden = NO;
|
||||||
[self updatePurchaseButtonState];
|
[self updatePurchaseButtonState];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
id sanitized = KBKeyboardSubscriptionSanitizeJSON(dataObj);
|
||||||
|
self.productsRawJSON = [sanitized isKindOfClass:NSArray.class] ? (NSArray *)sanitized : nil;
|
||||||
NSArray *models = [KBKeyboardSubscriptionProduct mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj];
|
NSArray *models = [KBKeyboardSubscriptionProduct mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj];
|
||||||
self.products = models ?: @[];
|
self.products = models ?: @[];
|
||||||
self.selectedIndex = self.products.count > 0 ? 0 : NSNotFound;
|
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 {
|
- (void)selectCurrentProductAnimated:(BOOL)animated {
|
||||||
if (self.selectedIndex == NSNotFound || self.selectedIndex >= self.products.count) { return; }
|
if (self.selectedIndex == NSNotFound || self.selectedIndex >= self.products.count) { return; }
|
||||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.selectedIndex inSection:0];
|
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.selectedIndex inSection:0];
|
||||||
@@ -401,4 +454,3 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
/// 键盘JSON数据
|
/// 键盘JSON数据
|
||||||
#define AppGroup_MyKbJson @"AppGroup_MyKbJson"
|
#define AppGroup_MyKbJson @"AppGroup_MyKbJson"
|
||||||
|
|
||||||
|
/// 键盘 -> 主 App 订阅页预填充数据(用于免二次请求)
|
||||||
|
#define AppGroup_SubscriptionPrefillPayload @"AppGroup_SubscriptionPrefillPayload"
|
||||||
|
|
||||||
/// 皮肤图标加载模式:
|
/// 皮肤图标加载模式:
|
||||||
/// 0 = 使用本地 Assets 图片名(key_icons 的 value 写成图片名,例如 "kb_q_melon")
|
/// 0 = 使用本地 Assets 图片名(key_icons 的 value 写成图片名,例如 "kb_q_melon")
|
||||||
/// 1 = 使用远程 Zip 皮肤包(skinJSON 中提供 zip_url;key_icons 的 value 写成 Zip 内图标文件名,例如 "key_a")
|
/// 1 = 使用远程 Zip 皮肤包(skinJSON 中提供 zip_url;key_icons 的 value 写成 Zip 内图标文件名,例如 "key_a")
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
#import "KBVipPay.h"
|
#import "KBVipPay.h"
|
||||||
#import "KBUserSessionManager.h"
|
#import "KBUserSessionManager.h"
|
||||||
#import "KBLoginVC.h"
|
#import "KBLoginVC.h"
|
||||||
|
#import "KBConfig.h"
|
||||||
|
|
||||||
|
static NSTimeInterval const kKBSubscriptionPrefillTTL = 10 * 60.0;
|
||||||
|
|
||||||
// 注意:用于判断系统已启用本输入法扩展的 bundle id 需与扩展 target 的
|
// 注意:用于判断系统已启用本输入法扩展的 bundle id 需与扩展 target 的
|
||||||
// PRODUCT_BUNDLE_IDENTIFIER 完全一致。
|
// PRODUCT_BUNDLE_IDENTIFIER 完全一致。
|
||||||
@@ -199,8 +202,36 @@
|
|||||||
if ([action isEqualToString:@"autopay"]) {
|
if ([action isEqualToString:@"autopay"]) {
|
||||||
autoPay = YES;
|
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];
|
KBVipPay *vc = [[KBVipPay alloc] init];
|
||||||
|
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];
|
[vc configureWithProductId:productId autoPurchase:autoPay];
|
||||||
|
}
|
||||||
[KB_CURRENT_NAV pushViewController:vc animated:true];
|
[KB_CURRENT_NAV pushViewController:vc animated:true];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
@@ -209,6 +240,23 @@
|
|||||||
return NO;
|
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<NSString *, NSString *> *)kb_queryParametersFromURL:(NSURL *)url {
|
- (NSDictionary<NSString *, NSString *> *)kb_queryParametersFromURL:(NSURL *)url {
|
||||||
if (!url) { return @{}; }
|
if (!url) { return @{}; }
|
||||||
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
/// 通过键盘深链配置初始商品及是否自动发起购买
|
/// 通过键盘深链配置初始商品及是否自动发起购买
|
||||||
- (void)configureWithProductId:(nullable NSString *)productId
|
- (void)configureWithProductId:(nullable NSString *)productId
|
||||||
autoPurchase:(BOOL)autoPurchase;
|
autoPurchase:(BOOL)autoPurchase;
|
||||||
|
|
||||||
|
/// 通过键盘扩展预填充商品列表(免二次请求)
|
||||||
|
- (void)configureWithProductId:(nullable NSString *)productId
|
||||||
|
autoPurchase:(BOOL)autoPurchase
|
||||||
|
prefillProductsJSON:(nullable NSArray *)productsJSON
|
||||||
|
selectedIndex:(NSInteger)selectedIndex;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#import "KBPayProductModel.h"
|
#import "KBPayProductModel.h"
|
||||||
#import "KBBizCode.h"
|
#import "KBBizCode.h"
|
||||||
#import "keyBoard-Swift.h"
|
#import "keyBoard-Swift.h"
|
||||||
|
#import <MJExtension/MJExtension.h>
|
||||||
|
|
||||||
static NSString * const kKBVipHeaderId = @"kKBVipHeaderId";
|
static NSString * const kKBVipHeaderId = @"kKBVipHeaderId";
|
||||||
static NSString * const kKBVipSubscribeCellId = @"kKBVipSubscribeCellId";
|
static NSString * const kKBVipSubscribeCellId = @"kKBVipSubscribeCellId";
|
||||||
@@ -37,6 +38,9 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
|
|||||||
@property (nonatomic, assign) BOOL pendingAutoPurchase;
|
@property (nonatomic, assign) BOOL pendingAutoPurchase;
|
||||||
@property (nonatomic, assign) BOOL hasTriggeredAutoPurchase;
|
@property (nonatomic, assign) BOOL hasTriggeredAutoPurchase;
|
||||||
@property (nonatomic, assign) BOOL viewVisible;
|
@property (nonatomic, assign) BOOL viewVisible;
|
||||||
|
@property (nonatomic, copy, nullable) NSArray *prefillProductsJSON;
|
||||||
|
@property (nonatomic, assign) NSInteger prefillSelectedIndex;
|
||||||
|
@property (nonatomic, assign) BOOL didApplyPrefillPlans;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -59,6 +63,7 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
|
|||||||
self.payVM = [PayVM new];
|
self.payVM = [PayVM new];
|
||||||
self.plans = @[];
|
self.plans = @[];
|
||||||
self.selectedIndex = NSNotFound;
|
self.selectedIndex = NSNotFound;
|
||||||
|
self.prefillSelectedIndex = NSNotFound;
|
||||||
|
|
||||||
// 组装主列表
|
// 组装主列表
|
||||||
[self.view addSubview:self.collectionView];
|
[self.view addSubview:self.collectionView];
|
||||||
@@ -101,9 +106,12 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
|
|||||||
|
|
||||||
// 预计算 Header 高度(由内部约束决定)
|
// 预计算 Header 高度(由内部约束决定)
|
||||||
self.headerHeight = [self kb_calcHeaderHeightForWidth:KB_SCREEN_WIDTH];
|
self.headerHeight = [self kb_calcHeaderHeightForWidth:KB_SCREEN_WIDTH];
|
||||||
|
BOOL appliedPrefill = [self kb_applyPrefillPlansIfPossible];
|
||||||
[self.collectionView reloadData];
|
[self.collectionView reloadData];
|
||||||
|
[self selectCurrentPlanAnimated:NO];
|
||||||
|
if (!appliedPrefill) {
|
||||||
[self fetchSubscriptionPlans];
|
[self fetchSubscriptionPlans];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated {
|
- (void)viewDidAppear:(BOOL)animated {
|
||||||
@@ -128,6 +136,26 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
|
|||||||
[self kb_triggerAutoPurchaseIfNeeded];
|
[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
|
#pragma mark - Data
|
||||||
- (void)fetchSubscriptionPlans {
|
- (void)fetchSubscriptionPlans {
|
||||||
__weak typeof(self) weakSelf = self;
|
__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<KBPayProductModel *> *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<KBPayProductModel *> *)plans {
|
- (void)prepareStoreKitWithPlans:(NSArray<KBPayProductModel *> *)plans {
|
||||||
if (![plans isKindOfClass:NSArray.class] || plans.count == 0) { return; }
|
if (![plans isKindOfClass:NSArray.class] || plans.count == 0) { return; }
|
||||||
NSMutableArray<NSString *> *ids = [NSMutableArray array];
|
NSMutableArray<NSString *> *ids = [NSMutableArray array];
|
||||||
|
|||||||
Reference in New Issue
Block a user