// // KBKeyboardSubscriptionView.m // CustomKeyboard // #import "KBKeyboardSubscriptionView.h" #import "KBKeyboardSubscriptionProduct.h" #import "KBNetworkManager.h" #import "KBFullAccessManager.h" #import "KBKeyboardSubscriptionFeatureMarqueeView.h" #import "KBKeyboardSubscriptionOptionCell.h" #import static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptionCellId"; @interface KBKeyboardSubscriptionView () @property (nonatomic, strong) UIImageView *cardView; @property (nonatomic, strong) UIButton *closeButton; @property (nonatomic, strong) KBKeyboardSubscriptionFeatureMarqueeView *featureMarqueeView; @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) UIButton *purchaseButton; @property (nonatomic, strong) UILabel *agreementLabel; @property (nonatomic, strong) UIButton *agreementButton; @property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator; @property (nonatomic, strong) UILabel *emptyLabel; @property (nonatomic, copy) NSArray *products; @property (nonatomic, assign) NSInteger selectedIndex; @property (nonatomic, assign) BOOL didLoadOnce; @property (nonatomic, assign, getter=isLoading) BOOL loading; @end @implementation KBKeyboardSubscriptionView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor clearColor]; _selectedIndex = NSNotFound; [self setupCardView]; [self setupFeatureItems]; } return self; } #pragma mark - Public - (void)refreshProductsIfNeeded { if (!self.didLoadOnce) { [self fetchProducts]; } } - (void)reloadProducts { [self fetchProducts]; } #pragma mark - UI - (void)setupCardView { [self addSubview:self.cardView]; [self.cardView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.mas_left).offset(0); make.right.equalTo(self.mas_right).offset(0); make.top.equalTo(self.mas_top).offset(0); make.bottom.equalTo(self.mas_bottom).offset(0); }]; [self.cardView addSubview:self.closeButton]; [self.cardView addSubview:self.featureMarqueeView]; [self.cardView addSubview:self.collectionView]; [self.cardView addSubview:self.purchaseButton]; [self.cardView addSubview:self.agreementLabel]; [self.cardView addSubview:self.agreementButton]; [self.cardView addSubview:self.loadingIndicator]; [self.cardView addSubview:self.emptyLabel]; [self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.cardView.mas_left).offset(12); make.top.equalTo(self.cardView.mas_top).offset(25); make.width.height.mas_equalTo(28); }]; [self.featureMarqueeView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.closeButton.mas_right).offset(5); make.centerY.equalTo(self.closeButton); make.right.equalTo(self.cardView.mas_right).offset(-12); make.height.mas_equalTo(48); }]; [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self).inset(16); make.top.equalTo(self.featureMarqueeView.mas_bottom).offset(0); make.height.mas_equalTo(76); }]; [self.purchaseButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.cardView.mas_left).offset(16); make.right.equalTo(self.cardView.mas_right).offset(-16); make.top.equalTo(self.collectionView.mas_bottom).offset(20); // make.bottom.equalTo(self.agreementLabel.mas_top).offset(-16); make.height.mas_greaterThanOrEqualTo(@45); }]; [self.agreementLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self.cardView.mas_centerX); make.top.equalTo(self.purchaseButton.mas_bottom).offset(8); }]; [self.agreementButton mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self.cardView.mas_centerX); make.top.equalTo(self.agreementLabel.mas_bottom).offset(4); }]; [self.loadingIndicator mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self.collectionView); }]; [self.emptyLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self.collectionView); }]; [self updatePurchaseButtonState]; } - (void)setupFeatureItems { NSArray *titles = @[ KBLocalized(@"Wireless Sub-ai\nDialogue"), KBLocalized(@"Personalized\nKeyboard"), KBLocalized(@"Chat\nPersona"), KBLocalized(@"Emotional\nCounseling") ]; NSArray *images = @[ [UIImage imageNamed:@"home_ai_icon"] ?: [UIImage new], [UIImage imageNamed:@"home_keyboard_icon"] ?: [UIImage new], [UIImage imageNamed:@"home_chat_icon"] ?: [UIImage new], [UIImage imageNamed:@"home_emotion_icon"] ?: [UIImage new] ]; [self.featureMarqueeView configureWithTitles:titles images:images]; } #pragma mark - Actions - (void)onTapClose { if ([self.delegate respondsToSelector:@selector(subscriptionViewDidTapClose:)]) { [self.delegate subscriptionViewDidTapClose:self]; } } - (void)onTapPurchase { if (self.selectedIndex == NSNotFound || self.selectedIndex >= self.products.count) { [KBHUD showInfo:KBLocalized(@"Please select a product")]; return; } KBKeyboardSubscriptionProduct *product = self.products[self.selectedIndex]; if ([self.delegate respondsToSelector:@selector(subscriptionView:didTapPurchaseForProduct:)]) { [self.delegate subscriptionView:self didTapPurchaseForProduct:product]; } } - (void)onTapAgreement { [KBHUD showInfo:KBLocalized(@"Agreement coming soon")]; } #pragma mark - Data - (void)fetchProducts { if (self.isLoading) { return; } if (![[KBFullAccessManager shared] hasFullAccess]) { [KBHUD showInfo:KBLocalized(@"Enable Full Access to continue")]; return; } self.loading = YES; self.emptyLabel.hidden = YES; [self.loadingIndicator startAnimating]; NSDictionary *params = @{@"type": @"subscription"}; __weak typeof(self) weakSelf = self; [[KBNetworkManager shared] GET:API_SUBSCRIPTION_PRODUCT_LIST parameters:params headers:nil completion:^(NSDictionary *json, NSURLResponse *response, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ __strong typeof(weakSelf) self = weakSelf; if (!self) { return; } self.loading = NO; [self.loadingIndicator stopAnimating]; if (error) { NSString *tip = error.localizedDescription ?: KBLocalized(@"Network error"); [KBHUD showInfo:tip]; self.products = @[]; self.selectedIndex = NSNotFound; [self.collectionView reloadData]; self.emptyLabel.hidden = NO; [self updatePurchaseButtonState]; return; } id dataObj = json[@"data"]; if (![dataObj isKindOfClass:[NSArray class]]) { dataObj = json[@"list"]; } if (![dataObj isKindOfClass:[NSArray class]]) { self.products = @[]; self.selectedIndex = NSNotFound; [self.collectionView reloadData]; self.emptyLabel.hidden = NO; [self updatePurchaseButtonState]; return; } NSArray *models = [KBKeyboardSubscriptionProduct mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj]; self.products = models ?: @[]; self.selectedIndex = self.products.count > 0 ? 0 : NSNotFound; self.emptyLabel.hidden = self.products.count > 0; [self.collectionView reloadData]; [self selectCurrentProductAnimated:NO]; [self updatePurchaseButtonState]; self.didLoadOnce = YES; }); }]; } - (void)selectCurrentProductAnimated:(BOOL)animated { if (self.selectedIndex == NSNotFound || self.selectedIndex >= self.products.count) { return; } NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.selectedIndex inSection:0]; [self.collectionView selectItemAtIndexPath:indexPath animated:animated scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; KBKeyboardSubscriptionOptionCell *cell = (KBKeyboardSubscriptionOptionCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if ([cell isKindOfClass:KBKeyboardSubscriptionOptionCell.class]) { [cell applySelected:YES animated:animated]; } } - (void)updatePurchaseButtonState { BOOL enabled = (self.products.count > 0); self.purchaseButton.enabled = enabled; self.purchaseButton.alpha = enabled ? 1.0 : 0.5; } #pragma mark - UICollectionView DataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.products.count; } - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { KBKeyboardSubscriptionOptionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBKeyboardSubscriptionCellId forIndexPath:indexPath]; if (indexPath.item < self.products.count) { KBKeyboardSubscriptionProduct *product = self.products[indexPath.item]; [cell configureWithProduct:product]; BOOL selected = (indexPath.item == self.selectedIndex); [cell applySelected:selected animated:NO]; } else { [cell configureWithProduct:nil]; [cell applySelected:NO animated:NO]; } return cell; } #pragma mark - UICollectionView Delegate - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.item >= self.products.count) { return; } NSInteger previous = self.selectedIndex; self.selectedIndex = indexPath.item; if (previous != NSNotFound && previous != indexPath.item) { NSIndexPath *prev = [NSIndexPath indexPathForItem:previous inSection:0]; KBKeyboardSubscriptionOptionCell *prevCell = (KBKeyboardSubscriptionOptionCell *)[collectionView cellForItemAtIndexPath:prev]; [prevCell applySelected:NO animated:YES]; } KBKeyboardSubscriptionOptionCell *cell = (KBKeyboardSubscriptionOptionCell *)[collectionView cellForItemAtIndexPath:indexPath]; [cell applySelected:YES animated:YES]; } #pragma mark - Layout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { // CGFloat width = MIN(MAX(collectionView.bounds.size.width * 0.56, 150), 220); return CGSizeMake(160, collectionView.bounds.size.height); } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 12.0; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { return UIEdgeInsetsMake(0, 0, 0, 0); } #pragma mark - Lazy - (UIImageView *)cardView { if (!_cardView) { _cardView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"keybord_bg_icon"]]; // _cardView.layer.cornerRadius = 20; // _cardView.layer.masksToBounds = YES; _cardView.userInteractionEnabled = YES; _cardView.contentMode = UIViewContentModeScaleAspectFill; } return _cardView; } - (UIButton *)closeButton { if (!_closeButton) { _closeButton = [UIButton buttonWithType:UIButtonTypeSystem]; _closeButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7]; _closeButton.layer.cornerRadius = 14; _closeButton.layer.masksToBounds = YES; [_closeButton setTitle:@"✕" forState:UIControlStateNormal]; [_closeButton setTitleColor:[UIColor colorWithHex:0x666666] forState:UIControlStateNormal]; _closeButton.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold]; [_closeButton addTarget:self action:@selector(onTapClose) forControlEvents:UIControlEventTouchUpInside]; } return _closeButton; } - (KBKeyboardSubscriptionFeatureMarqueeView *)featureMarqueeView { if (!_featureMarqueeView) { _featureMarqueeView = [[KBKeyboardSubscriptionFeatureMarqueeView alloc] init]; } return _featureMarqueeView; } - (UICollectionView *)collectionView { if (!_collectionView) { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; _collectionView.backgroundColor = [UIColor clearColor]; _collectionView.showsHorizontalScrollIndicator = NO; _collectionView.dataSource = self; _collectionView.delegate = self; [_collectionView registerClass:KBKeyboardSubscriptionOptionCell.class forCellWithReuseIdentifier:kKBKeyboardSubscriptionCellId]; } return _collectionView; } - (UIButton *)purchaseButton { if (!_purchaseButton) { _purchaseButton = [UIButton buttonWithType:UIButtonTypeSystem]; _purchaseButton.layer.cornerRadius = 26; _purchaseButton.layer.masksToBounds = YES; [_purchaseButton setTitle:KBLocalized(@"Recharge Now") forState:UIControlStateNormal]; _purchaseButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold]; [_purchaseButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [_purchaseButton setBackgroundImage:[self imageWithColor:[UIColor colorWithHex:0x02BEAC]] forState:UIControlStateNormal]; [_purchaseButton addTarget:self action:@selector(onTapPurchase) forControlEvents:UIControlEventTouchUpInside]; } return _purchaseButton; } - (UILabel *)agreementLabel { if (!_agreementLabel) { _agreementLabel = [[UILabel alloc] init]; _agreementLabel.text = KBLocalized(@"By clicking \"pay\", you agree to the"); _agreementLabel.font = [UIFont systemFontOfSize:11]; _agreementLabel.textColor = [UIColor colorWithHex:0x4A4A4A]; } return _agreementLabel; } - (UIButton *)agreementButton { if (!_agreementButton) { _agreementButton = [UIButton buttonWithType:UIButtonTypeSystem]; [_agreementButton setTitle:KBLocalized(@"Membership Agreement") forState:UIControlStateNormal]; _agreementButton.titleLabel.font = [UIFont systemFontOfSize:11 weight:UIFontWeightSemibold]; [_agreementButton setTitleColor:[UIColor colorWithHex:0x02BEAC] forState:UIControlStateNormal]; [_agreementButton addTarget:self action:@selector(onTapAgreement) forControlEvents:UIControlEventTouchUpInside]; } return _agreementButton; } - (UIActivityIndicatorView *)loadingIndicator { if (!_loadingIndicator) { _loadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; _loadingIndicator.hidesWhenStopped = YES; } return _loadingIndicator; } - (UILabel *)emptyLabel { if (!_emptyLabel) { _emptyLabel = [[UILabel alloc] init]; _emptyLabel.text = KBLocalized(@"No products available"); _emptyLabel.font = [UIFont systemFontOfSize:13]; _emptyLabel.textColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; _emptyLabel.textAlignment = NSTextAlignmentCenter; _emptyLabel.hidden = YES; } return _emptyLabel; } - (UIImage *)imageWithColor:(UIColor *)color { CGSize size = CGSizeMake(1, 1); UIGraphicsBeginImageContextWithOptions(size, NO, 0); [color setFill]; UIRectFill(CGRectMake(0, 0, size.width, size.height)); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } @end