diff --git a/CustomKeyboard/View/KBKeyboardSubscriptionFeatureItemView.h b/CustomKeyboard/View/KBKeyboardSubscriptionFeatureItemView.h new file mode 100644 index 0000000..2951def --- /dev/null +++ b/CustomKeyboard/View/KBKeyboardSubscriptionFeatureItemView.h @@ -0,0 +1,21 @@ +// +// KBKeyboardSubscriptionFeatureItemView.h +// CustomKeyboard +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 顶部滚动的功能点 Item(左图右文) +@interface KBKeyboardSubscriptionFeatureItemView : UIView + +- (void)configureWithImage:(UIImage *)image title:(NSString *)title; + +/// 根据 title 计算推荐宽度:textWidth + 50(图片 35 + 间距 5 + 左右内边距各 5) ++ (CGFloat)preferredWidthForTitle:(NSString *)title; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/CustomKeyboard/View/KBKeyboardSubscriptionFeatureItemView.m b/CustomKeyboard/View/KBKeyboardSubscriptionFeatureItemView.m new file mode 100644 index 0000000..1cd5dd2 --- /dev/null +++ b/CustomKeyboard/View/KBKeyboardSubscriptionFeatureItemView.m @@ -0,0 +1,86 @@ +// +// KBKeyboardSubscriptionFeatureItemView.m +// CustomKeyboard +// + +#import "KBKeyboardSubscriptionFeatureItemView.h" +#import "Masonry.h" + +@interface KBKeyboardSubscriptionFeatureItemView () +@property (nonatomic, strong) UIImageView *iconView; +@property (nonatomic, strong) UILabel *titleLabel; +@end + +@implementation KBKeyboardSubscriptionFeatureItemView + +static const CGFloat kKBFeatureItemPadding = 5.0; +static const CGFloat kKBFeatureItemIconSize = 35.0; +static const CGFloat kKBFeatureItemGap = 5.0; + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { +// self.layer.cornerRadius = 24; +// self.layer.masksToBounds = YES; +// self.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.85]; + + [self addSubview:self.iconView]; + [self addSubview:self.titleLabel]; + + [self.iconView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.mas_left).offset(kKBFeatureItemPadding); + make.centerY.equalTo(self.mas_centerY); + make.width.height.mas_equalTo(kKBFeatureItemIconSize); + }]; + + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.iconView.mas_right).offset(kKBFeatureItemGap); + make.centerY.equalTo(self.mas_centerY); + make.right.equalTo(self.mas_right).offset(-kKBFeatureItemPadding); + }]; + } + return self; +} + +- (void)configureWithImage:(UIImage *)image title:(NSString *)title { + self.iconView.image = image; + self.titleLabel.text = title ?: @""; +} + ++ (CGFloat)preferredWidthForTitle:(NSString *)title { + UIFont *font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold]; + NSString *text = title ?: @""; + NSArray *lines = [text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + CGFloat maxLineWidth = 0; + for (NSString *line in lines) { + NSString *trimLine = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if (trimLine.length == 0) { continue; } + CGSize size = [trimLine sizeWithAttributes:@{NSFontAttributeName: font}]; + maxLineWidth = MAX(maxLineWidth, ceil(size.width)); + } + if (maxLineWidth <= 0) { maxLineWidth = 80; } + + CGFloat width = maxLineWidth + 50.0; // 5 + 35 + 5 + 5 + width = MIN(MAX(width, 120.0), 240.0); + return width; +} + +- (UIImageView *)iconView { + if (!_iconView) { + _iconView = [[UIImageView alloc] init]; + _iconView.contentMode = UIViewContentModeScaleAspectFit; + } + return _iconView; +} + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [[UILabel alloc] init]; + _titleLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold]; + _titleLabel.textColor = [UIColor colorWithHex:0x4A4A4A]; + _titleLabel.numberOfLines = 0; + _titleLabel.lineBreakMode = NSLineBreakByWordWrapping; + } + return _titleLabel; +} + +@end diff --git a/CustomKeyboard/View/KBKeyboardSubscriptionView.m b/CustomKeyboard/View/KBKeyboardSubscriptionView.m index 11b6476..8c57468 100644 --- a/CustomKeyboard/View/KBKeyboardSubscriptionView.m +++ b/CustomKeyboard/View/KBKeyboardSubscriptionView.m @@ -7,6 +7,7 @@ #import "KBKeyboardSubscriptionProduct.h" #import "KBNetworkManager.h" #import "KBFullAccessManager.h" +#import "KBKeyboardSubscriptionFeatureItemView.h" #import static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptionCellId"; @@ -19,7 +20,6 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio @interface KBKeyboardSubscriptionView () @property (nonatomic, strong) UIImageView *cardView; @property (nonatomic, strong) UIButton *closeButton; -@property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UIScrollView *featureScrollView; @property (nonatomic, strong) UIView *featureContentView; @property (nonatomic, strong) UICollectionView *collectionView; @@ -55,7 +55,6 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio - (void)layoutSubviews { [super layoutSubviews]; - self.featureLoopWidth = self.featureScrollView.contentSize.width * 0.5f; [self startFeatureTickerIfNeeded]; } @@ -103,7 +102,6 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio }]; [self.cardView addSubview:self.closeButton]; - [self.cardView addSubview:self.titleLabel]; [self.cardView addSubview:self.featureScrollView]; [self.cardView addSubview:self.collectionView]; [self.cardView addSubview:self.purchaseButton]; @@ -114,25 +112,20 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio [self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.cardView.mas_left).offset(12); - make.top.equalTo(self.cardView.mas_top).offset(10); + make.top.equalTo(self.cardView.mas_top).offset(25); make.width.height.mas_equalTo(28); }]; - [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.centerY.equalTo(self.closeButton.mas_centerY); - make.centerX.equalTo(self.cardView.mas_centerX); - }]; - [self.featureScrollView mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(self.closeButton.mas_bottom).offset(8); - make.left.equalTo(self.cardView.mas_left).offset(12); + 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(54); + make.height.mas_equalTo(48); }]; [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.featureScrollView); - make.top.equalTo(self.featureScrollView.mas_bottom).offset(12); + make.top.equalTo(self.featureScrollView.mas_bottom).offset(10); make.height.mas_equalTo(138); }]; @@ -173,28 +166,66 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio } - (void)setupFeatureItems { - self.featureItems = @[ - @{@"emoji": @"🤖", @"title": KBLocalized(@"Wireless Sub-ai Dialogue")}, - @{@"emoji": @"⌨️", @"title": KBLocalized(@"Personalized Keyboard")}, - @{@"emoji": @"🧠", @"title": KBLocalized(@"Creative Prompt Library")}, - @{@"emoji": @"💬", @"title": KBLocalized(@"Chat Personalized Coach")} + 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] + ]; + NSMutableArray *items = [NSMutableArray array]; + [titles enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { + UIImage *img = (idx < images.count) ? images[idx] : nil; + if (!obj) { obj = @""; } + if (!img) { img = [UIImage new]; } + [items addObject:@{@"title": obj, @"image": img}]; + }]; + self.featureItems = [items copy]; [self rebuildFeatureBadges]; } - (void)rebuildFeatureBadges { [self.featureContentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; - NSArray *loopData = [self.featureItems arrayByAddingObjectsFromArray:self.featureItems]; - UIView *previous = nil; + if (self.featureItems.count == 0) { + self.featureScrollView.contentSize = CGSizeZero; + self.featureLoopWidth = 0; + [self stopFeatureTicker]; + return; + } + BOOL shouldLoop = (self.featureItems.count > 1); CGFloat spacing = 12.0; - for (NSDictionary *info in loopData) { - UIView *badge = [self buildBadgeWithEmoji:info[@"emoji"] title:info[@"title"]]; + NSInteger baseCount = self.featureItems.count; + + NSMutableArray *baseWidths = [NSMutableArray arrayWithCapacity:baseCount]; + CGFloat baseTotalWidth = 0.0; + for (NSInteger i = 0; i < baseCount; i++) { + NSDictionary *info = self.featureItems[i]; + NSString *title = [info[@"title"] isKindOfClass:NSString.class] ? info[@"title"] : @""; + CGFloat width = [KBKeyboardSubscriptionFeatureItemView preferredWidthForTitle:title]; + [baseWidths addObject:@(width)]; + baseTotalWidth += width; + if (i > 0) { baseTotalWidth += spacing; } + } + + NSArray *loopData = shouldLoop ? [self.featureItems arrayByAddingObjectsFromArray:self.featureItems] : self.featureItems; + CGFloat totalWidth = shouldLoop ? (baseTotalWidth * 2 + spacing) : baseTotalWidth; + + UIView *previous = nil; + for (NSInteger idx = 0; idx < loopData.count; idx++) { + NSDictionary *info = loopData[idx]; + UIImage *img = [info[@"image"] isKindOfClass:UIImage.class] ? info[@"image"] : nil; + NSString *title = [info[@"title"] isKindOfClass:NSString.class] ? info[@"title"] : @""; + + CGFloat width = (baseCount > 0) ? baseWidths[(NSUInteger)(idx % baseCount)].doubleValue : 120.0; + KBKeyboardSubscriptionFeatureItemView *badge = [[KBKeyboardSubscriptionFeatureItemView alloc] init]; + [badge configureWithImage:(img ?: [UIImage new]) title:title]; [self.featureContentView addSubview:badge]; - CGFloat textWidth = [info[@"title"] boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 24) - options:NSStringDrawingUsesLineFragmentOrigin - attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold]} - context:nil].size.width; - CGFloat width = MIN(MAX(textWidth + 60, 150), 240); + [badge mas_makeConstraints:^(MASConstraintMaker *make) { make.top.bottom.equalTo(self.featureContentView); make.width.mas_equalTo(width); @@ -206,6 +237,7 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio }]; previous = badge; } + __weak typeof(self) weakSelf = self; [self.featureContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.top.bottom.equalTo(weakSelf.featureScrollView); @@ -217,37 +249,21 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio make.right.equalTo(weakSelf.featureScrollView); } }]; -} - -- (UIView *)buildBadgeWithEmoji:(NSString *)emoji title:(NSString *)title { - UIView *container = [[UIView alloc] init]; - container.layer.cornerRadius = 24; - container.layer.masksToBounds = YES; - container.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.85]; - - UILabel *emojiLabel = [[UILabel alloc] init]; - emojiLabel.text = emoji ?: @"✨"; - emojiLabel.font = [UIFont systemFontOfSize:20]; - - UILabel *textLabel = [[UILabel alloc] init]; - textLabel.text = title ?: @""; - textLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold]; - textLabel.textColor = [UIColor colorWithHex:0x4A4A4A]; - - [container addSubview:emojiLabel]; - [container addSubview:textLabel]; - - [emojiLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.equalTo(container.mas_left).offset(14); - make.centerY.equalTo(container.mas_centerY); - }]; - - [textLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.equalTo(emojiLabel.mas_right).offset(10); - make.centerY.equalTo(container.mas_centerY); - make.right.equalTo(container.mas_right).offset(-14); - }]; - return container; + CGFloat minWidth = CGRectGetWidth(self.featureScrollView.bounds); + if (minWidth <= 0) { minWidth = [UIScreen mainScreen].bounds.size.width - 24; } + CGFloat height = CGRectGetHeight(self.featureScrollView.bounds); + if (height <= 0) { height = 48; } + CGFloat contentWidth = totalWidth; + if (contentWidth <= minWidth) { + contentWidth = minWidth; + self.featureLoopWidth = 0; + [self stopFeatureTicker]; + self.featureScrollView.contentOffset = CGPointZero; + } else { + self.featureLoopWidth = shouldLoop ? (baseTotalWidth + spacing) : 0.0; + [self startFeatureTickerIfNeeded]; + } + self.featureScrollView.contentSize = CGSizeMake(contentWidth, height); } #pragma mark - Actions @@ -423,10 +439,10 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio - (UIImageView *)cardView { if (!_cardView) { _cardView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"keybord_bg_icon"]]; -// _cardView.layer.cornerRadius = 20; -// _cardView.layer.masksToBounds = YES; + _cardView.layer.cornerRadius = 20; + _cardView.layer.masksToBounds = YES; + _cardView.userInteractionEnabled = YES; _cardView.contentMode = UIViewContentModeScaleAspectFill; - _cardView.clipsToBounds = true; } return _cardView; } @@ -445,16 +461,6 @@ static NSString * const kKBKeyboardSubscriptionCellId = @"kKBKeyboardSubscriptio return _closeButton; } -- (UILabel *)titleLabel { - if (!_titleLabel) { - _titleLabel = [[UILabel alloc] init]; - _titleLabel.text = KBLocalized(@"Premium Subscription"); - _titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold]; - _titleLabel.textColor = [UIColor colorWithHex:0x24556B]; - } - return _titleLabel; -} - - (UIScrollView *)featureScrollView { if (!_featureScrollView) { _featureScrollView = [[UIScrollView alloc] init]; diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index f71e586..6d8dc0b 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -216,6 +216,7 @@ ECC9EE02174D86E8D792472F /* Pods_keyBoard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 967065BB5230E43F293B3AF9 /* Pods_keyBoard.framework */; }; 04FEDC122F00010000999999 /* KBKeyboardSubscriptionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC112F00010000999999 /* KBKeyboardSubscriptionView.m */; }; 04FEDC222F00020000999999 /* KBKeyboardSubscriptionProduct.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC212F00020000999999 /* KBKeyboardSubscriptionProduct.m */; }; + 04FEDC322F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC312F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -539,6 +540,8 @@ 04FC95B12EB0B2CC007BD342 /* KBSettingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSettingView.m; sourceTree = ""; }; 04FEDC102F00010000999999 /* KBKeyboardSubscriptionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardSubscriptionView.h; sourceTree = ""; }; 04FEDC112F00010000999999 /* KBKeyboardSubscriptionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardSubscriptionView.m; sourceTree = ""; }; + 04FEDC302F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardSubscriptionFeatureItemView.h; sourceTree = ""; }; + 04FEDC312F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardSubscriptionFeatureItemView.m; sourceTree = ""; }; 04FC95C72EB1E4C9007BD342 /* BaseNavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BaseNavigationController.h; sourceTree = ""; }; 04FC95C82EB1E4C9007BD342 /* BaseNavigationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BaseNavigationController.m; sourceTree = ""; }; 04FC95CA2EB1E780007BD342 /* BaseTabBarController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BaseTabBarController.h; sourceTree = ""; }; @@ -1139,6 +1142,8 @@ 04FC95B12EB0B2CC007BD342 /* KBSettingView.m */, 04FEDC102F00010000999999 /* KBKeyboardSubscriptionView.h */, 04FEDC112F00010000999999 /* KBKeyboardSubscriptionView.m */, + 04FEDC302F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.h */, + 04FEDC312F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m */, 049FB22D2EC34EB900FAB05D /* KBStreamTextView.h */, 049FB22E2EC34EB900FAB05D /* KBStreamTextView.m */, 049FB23A2EC4766700FAB05D /* Function */, @@ -1843,6 +1848,7 @@ 04FEDAA12EEDB00100123456 /* KBEmojiDataProvider.m in Sources */, 04FC95B22EB0B2CC007BD342 /* KBSettingView.m in Sources */, 04FEDC122F00010000999999 /* KBKeyboardSubscriptionView.m in Sources */, + 04FEDC322F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m in Sources */, 04791F8E2ED469C0004E8522 /* KBHostAppLauncher.m in Sources */, 049FB23B2EC4766700FAB05D /* KBFunctionTagListView.m in Sources */, 049FB23C2EC4766700FAB05D /* KBStreamOverlayView.m in Sources */,