diff --git a/.kilocode/rules/memory-bank/tasks.md b/.kilocode/rules/memory-bank/tasks.md index b523f69..dc720d0 100644 --- a/.kilocode/rules/memory-bank/tasks.md +++ b/.kilocode/rules/memory-bank/tasks.md @@ -1,5 +1,141 @@ # 常见任务和模式 +## 图标库使用规范 - 禁止使用 MaterialIcons + +**最后更新**: 2025-10-24 + +### 重要规则 +**项目中不允许使用 MaterialIcons**,所有图标必须使用 Ionicons 以保持图标库的一致性。 + +### 问题描述 +在项目中发现使用 MaterialIcons 的情况,需要将所有 MaterialIcons 替换为 Ionicons,以保持图标库的一致性。 + +### 解决方案 +将所有 MaterialIcons 导入和使用替换为对应的 Ionicons。 + +### 实现模式 + +#### 1. 替换导入语句 +```typescript +// ❌ 禁止使用 +import { MaterialIcons } from '@expo/vector-icons'; + +// ✅ 正确写法 +import { Ionicons } from '@expo/vector-icons'; +``` + +#### 2. 替换图标名称和属性 +```typescript +// ❌ 禁止使用 + + +// ✅ 正确写法 - 使用 HeaderBar 中的返回按钮实现 + +``` + +#### 3. 常见图标映射 +- `arrow-back-ios` → `chevron-back` (返回按钮) +- `auto-awesome` → `star` (星星/自动推荐) +- `tips-and-updates` → `bulb` (提示/建议) +- `fact-check` → `checkbox` (检查/确认) +- `check-circle` → `checkmark-circle` (勾选圆圈) +- `remove` → `remove` (移除/删除,名称相同) + +### 重要注意事项 +1. **图标大小调整**:Ionicons 和 MaterialIcons 的默认大小可能不同,需要适当调整 +2. **图标名称差异**:两个图标库的图标名称不同,需要找到对应的功能图标 +3. **样式一致性**:确保替换后的图标在视觉上与原设计保持一致 +4. **Liquid Glass 兼容性**:替换后的图标需要继续支持 Liquid Glass 效果 +5. **代码审查**:在代码审查中需要特别检查是否使用了 MaterialIcons + +### 参考实现 +- `components/ui/HeaderBar.tsx` - 返回按钮的标准实现 +- `components/model/MembershipModal.tsx` - 完整的 MaterialIcons 替换示例 + +## 按钮组件 Liquid Glass 兼容性 + +**最后更新**: 2025-10-24 + +### 重要原则 +**所有按钮组件都需要尝试兼容 Liquid Glass**,这是项目的设计要求。 + +### 实现模式 + +#### 1. 导入必要的组件 +```typescript +import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; +``` + +#### 2. 检查设备支持情况 +```typescript +const isGlassAvailable = isLiquidGlassAvailable(); +``` + +#### 3. 实现条件渲染的按钮 +```typescript + + {isLiquidGlassAvailable() ? ( + + + + ) : ( + + + + )} + +``` + +#### 4. 定义样式 +```typescript +const styles = StyleSheet.create({ + button: { + width: 40, + height: 40, + borderRadius: 20, // 圆形按钮 + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', // 保证玻璃边界圆角效果 + // 其他通用样式... + }, + fallbackButton: { + backgroundColor: 'rgba(255, 255, 255, 0.9)', + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.3)', + }, +}); +``` + +### 重要注意事项 +1. **兼容性检查**:必须使用 `isLiquidGlassAvailable()` 检查设备支持情况 +2. **overflow: 'hidden'**:GlassView 组件需要设置此属性以保证圆角效果 +3. **降级样式**:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案 +4. **交互反馈**:设置 `isInteractive={true}` 启用原生的触觉反馈 +5. **图标居中**:确保使用 `alignItems: 'center'` 和 `justifyContent: 'center'` 使图标完全居中 +6. **色调自定义**:通过 `tintColor` 属性自定义按钮的颜色主题 + +### 常用配置 +- **glassEffectStyle**: "clear"(透明)或 "regular"(常规) +- **tintColor**: 根据按钮功能选择合适的颜色 + - 返回/导航操作:白色系 `rgba(255, 255, 255, 0.3)` + - 删除操作:红色系 `rgba(244, 67, 54, 0.2)` + - 确认操作:绿色系 `rgba(76, 175, 80, 0.2)` + - 信息操作:蓝色系 `rgba(33, 150, 243, 0.2)` + +### 参考实现 +- `components/model/MembershipModal.tsx` - 悬浮返回按钮 +- `components/glass/button.tsx` - 通用 Glass 按钮组件 +- `app/(tabs)/_layout.tsx` - 标签栏按钮实现 + ## HeaderBar 顶部距离处理 **最后更新**: 2025-10-16 diff --git a/components/model/MembershipModal.tsx b/components/model/MembershipModal.tsx index 5b25365..45b4601 100644 --- a/components/model/MembershipModal.tsx +++ b/components/model/MembershipModal.tsx @@ -11,8 +11,10 @@ import { captureUserAction } from '@/utils/sentry.utils'; import { Toast as GlobalToast } from '@/utils/toast.utils'; -import { MaterialIcons } from '@expo/vector-icons'; +import { Ionicons } from '@expo/vector-icons'; import { captureException } from '@sentry/react-native'; +import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; +import { LinearGradient } from 'expo-linear-gradient'; import React, { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, @@ -42,6 +44,8 @@ interface MembershipPlan { subtitle: string; type: 'weekly' | 'quarterly' | 'lifetime'; recommended?: boolean; + tag?: string; + originalPrice?: string; } const DEFAULT_PLANS: MembershipPlan[] = [ @@ -51,21 +55,50 @@ const DEFAULT_PLANS: MembershipPlan[] = [ subtitle: '一次投入,终身健康陪伴', type: 'lifetime', recommended: true, + tag: '限时特价', + originalPrice: '¥898', }, { id: 'com.anonymous.digitalpilates.membership.quarter', fallbackTitle: '季度会员', subtitle: '3个月蜕变计划,见证身材变化', type: 'quarterly', + originalPrice: '¥598', }, { id: 'com.anonymous.digitalpilates.membership.weekly', fallbackTitle: '周会员', subtitle: '7天体验,开启健康第一步', type: 'weekly', + originalPrice: '¥128', }, ]; + +const BENEFIT_COMPARISON = [ + { title: 'AI拍照记录热量', vip: true, regular: false }, + { title: 'AI拍照识别包装', vip: true, regular: false }, + { title: '私人饮食建议', vip: true, regular: false }, + { title: '定制健身训练', vip: true, regular: false }, + { title: '每日健康提醒', vip: true, regular: true }, +]; + +const PLAN_STYLE_CONFIG: Record = { + lifetime: { + gradient: ['#FFF1DD', '#FFE8FA'] as const, + accent: '#7B2CBF', + }, + quarterly: { + gradient: ['#E5EAFE', '#F4E8FF'] as const, + accent: '#5B5BE6', + }, + weekly: { + gradient: ['#FFF6E5', '#FFE7CC'] as const, + accent: '#FF9500', + }, +}; + + export function MembershipModal({ visible, onClose, onPurchaseSuccess }: MembershipModalProps) { const [selectedProduct, setSelectedProduct] = useState(null); const [loading, setLoading] = useState(false); @@ -672,38 +705,6 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members } }; - const renderMembershipBenefits = () => ( - - - 解锁全部健康功能 - 开启您的健康蜕变之旅 - - - - - 高级营养分析,精准卡路里计算 - - - - - 定制化减脂计划,科学体重管理 - - - - - 深度健康数据分析,洞察身体变化 - - - - {/* 皇冠图标 */} - - {/* */} - - - ); const renderPlanCard = (product: PurchasesStoreProduct) => { const plan = DEFAULT_PLANS.find(p => p.id === product.identifier); @@ -715,13 +716,14 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members const isSelected = selectedProduct === product; const displayTitle = product.title || plan.fallbackTitle; const priceLabel = product.priceString || ''; + const styleConfig = PLAN_STYLE_CONFIG[plan.type]; return ( !loading && product && setSelectedProduct(product)} @@ -732,35 +734,27 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members accessibilityHint={loading ? '购买进行中,无法切换套餐' : `选择${displayTitle}套餐`} accessibilityState={{ disabled: loading, selected: isSelected }} > - {plan.recommended && ( - - 推荐 - - )} + + {plan.tag && ( + + {plan.tag} + + )} - - - {displayTitle} - - - - - + {displayTitle} + {priceLabel || '--'} - - - {plan.subtitle} + {plan.originalPrice && ( + {plan.originalPrice} + )} + {plan.subtitle} + ); }; @@ -770,7 +764,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members @@ -781,119 +775,194 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members onPress={onClose} // 阻止点击背景关闭 /> - {/* 会员内容 */} - - {/* 关闭按钮 */} - {/* - × - */} - - - {/* 会员权益介绍 */} - {renderMembershipBenefits()} - - {/* 会员套餐选择 */} - {products.length === 0 && ( - - - 暂未获取到会员商品,请在 RevenueCat 中配置 iOS 产品并同步到当前 Offering。 - + {/* 悬浮返回按钮 - 移到 ScrollView 外部以确保始终在最上层 */} + + {isLiquidGlassAvailable() ? ( + + + + ) : ( + + )} - - {products.map(renderPlanCard)} - + - {/* 产品选择提示区域 */} - {selectedProduct && ( - - - {getTipsContent(selectedProduct)} - + + + + + + + + 会员套餐 - )} + 灵活选择,跟随节奏稳步提升 - {/* 协议同意区域 */} - - - 我已阅读并同意 - { - Linking.openURL(USER_AGREEMENT_URL); - captureMessage('click user agreement'); - }}> - 《用户协议》 - - | - { - // Linking.openURL(MEMBERSHIP_AGREEMENT_URL); - captureMessage('click membership agreement'); - }}> - 《会员协议》 - - | - { - // Linking.openURL(AUTO_RENEWAL_AGREEMENT_URL); - captureMessage('click auto renewal agreement'); - }}> - 《自动续费协议》 - + {products.length === 0 ? ( + + + 暂未获取到会员商品,请在 RevenueCat 中配置 iOS 产品并同步到当前 Offering。 + + + ) : ( + <> + + {products.map(renderPlanCard)} + + {selectedProduct && ( + + + {getTipsContent(selectedProduct)} + + )} + + )} - - {/* 购买按钮 */} - - {loading ? ( - - - 正在处理购买... + + + + - ) : ( - 开启健康蜕变 - )} - + 权益对比 + + 核心权益一目了然,选择更安心 - {/* 恢复购买按钮 */} - - {restoring ? ( - - - 恢复中... + + + 权益 + VIP + 普通用户 - ) : ( - 恢复购买 - )} - + {BENEFIT_COMPARISON.map((row, index) => ( + + {row.title} + + {row.vip ? ( + + ) : ( + + )} + + + {row.regular ? ( + + ) : ( + + )} + + + ))} + + + + + + 667+人正在定制私人计划 + + + + {loading ? ( + + + 正在处理购买... + + ) : ( + 立即订阅 + )} + + + + + 开通即视为同意 + { + Linking.openURL(USER_AGREEMENT_URL); + captureMessage('click user agreement'); + }} + > + 《用户协议》 + + | + { + captureMessage('click membership agreement'); + }} + > + 《会员协议》 + + | + { + captureMessage('click auto renewal agreement'); + }} + > + 《自动续费协议》 + + + + + {restoring ? ( + + + 恢复中... + + ) : ( + 恢复购买 + )} + + @@ -908,231 +977,284 @@ const styles = StyleSheet.create({ }, backdrop: { ...StyleSheet.absoluteFillObject, - backgroundColor: 'rgba(0, 0, 0, 0.3)', + backgroundColor: 'rgba(0, 0, 0, 0.35)', }, modalContainer: { - height: height * 0.6, - backgroundColor: 'white', - borderTopLeftRadius: 20, - borderTopRightRadius: 20, + height: height * 0.92, + backgroundColor: '#F5F6FA', + borderTopLeftRadius: 28, + borderTopRightRadius: 28, overflow: 'hidden', }, modalContent: { flex: 1, + }, + modalContentContainer: { paddingHorizontal: 20, - paddingTop: 20, + paddingBottom: 32, + paddingTop: 16, }, - closeButton: { - position: 'absolute', - top: 0, - right: 0, - width: 30, - height: 30, - borderRadius: 15, - backgroundColor: 'rgba(0, 0, 0, 0.1)', - justifyContent: 'center', + floatingBackButton: { + width: 40, + height: 40, + borderRadius: 20, alignItems: 'center', - zIndex: 1, + justifyContent: 'center', + overflow: 'hidden', }, - closeButtonText: { + floatingBackButtonContainer: { + position: 'absolute', + top: 20, + left: 20, + zIndex: 10, // 确保按钮在最上层 + }, + fallbackBackButton: { + backgroundColor: 'rgba(255, 255, 255, 0.9)', + + }, + sectionCard: { + backgroundColor: '#FFFFFF', + borderRadius: 20, + paddingHorizontal: 20, + paddingVertical: 18, + marginBottom: 20, + shadowColor: '#1C1C1E', + shadowOpacity: 0.06, + shadowRadius: 12, + shadowOffset: { width: 0, height: 6 }, + elevation: 2, + }, + sectionTitleRow: { + flexDirection: 'row', + alignItems: 'center', + }, + sectionTitleBadge: { + width: 28, + height: 28, + borderRadius: 14, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#F4EFFD', + marginRight: 8, + }, + sectionTitle: { fontSize: 18, - color: '#666', - fontWeight: '300', + fontWeight: '700', + color: '#2B2B2E', + }, + sectionSubtitle: { + fontSize: 13, + color: '#6B6B73', + marginTop: 6, + marginBottom: 16, }, configurationNotice: { - backgroundColor: '#FFF9E6', - borderRadius: 12, + borderRadius: 16, padding: 16, - marginTop: 30, - marginBottom: 20, - alignItems: 'center', - borderWidth: 1, - borderColor: '#FFE4B5', + backgroundColor: '#FFF4E5', }, configurationText: { fontSize: 14, - color: '#B8860B', + color: '#B86A04', textAlign: 'center', - marginTop: 8, - marginBottom: 12, - }, - configurationButton: { - backgroundColor: '#FF9500', - borderRadius: 8, - paddingHorizontal: 16, - paddingVertical: 8, - }, - configurationButtonText: { - color: 'white', - fontSize: 12, - fontWeight: '600', - }, - benefitsContainer: { - position: 'relative', - backgroundColor: '#FFE7F9', - borderRadius: 16, - marginBottom: 30, - padding: 20, - }, - benefitsTitleContainer: { - alignItems: 'center', - marginBottom: 20, - }, - benefitsTitle: { - fontSize: 18, - fontWeight: 'bold', - color: '#333', - marginBottom: 5, - }, - benefitsSubtitle: { - fontSize: 14, - color: '#666', - textAlign: 'center', - }, - benefitsTitleBg: { - width: 180, - height: 20, - marginBottom: 10, - }, - benefitItem: { - flexDirection: 'row', - alignItems: 'center', - marginTop: 10, - }, - benefitText: { - fontSize: 14, - color: '#333', - marginLeft: 8, - fontWeight: '500', - }, - crownContainer: { - position: 'absolute', - top: 10, - right: 10, - width: 100, - height: 100, - justifyContent: 'center', - alignItems: 'center', - }, - crownIcon: { - width: 80, - height: 80, - resizeMode: 'contain', + lineHeight: 20, }, plansContainer: { flexDirection: 'row', justifyContent: 'space-between', - marginBottom: 20, }, - planCard: { + planCardWrapper: { flex: 1, marginHorizontal: 4, - borderRadius: 12, - borderWidth: 2, - borderColor: '#E0E0E0', - paddingVertical: 20, - paddingHorizontal: 12, - alignItems: 'center', - position: 'relative', - backgroundColor: 'white', + borderRadius: 18, + overflow: 'hidden', + borderWidth: 1, + borderColor: 'rgba(123,44,191,0.08)', }, - selectedPlan: { - borderColor: '#DF42D0', - backgroundColor: '#FFF5FE', + planCardWrapperSelected: { + borderColor: '#7B2CBF', + shadowColor: '#7B2CBF', + shadowOpacity: 0.18, + shadowRadius: 16, + shadowOffset: { width: 0, height: 8 }, + elevation: 4, }, - recommendedBadge: { - position: 'absolute', - top: -10, - backgroundColor: '#7B2CBF', - borderRadius: 10, + planCardGradient: { + paddingHorizontal: 16, + paddingVertical: 18, + minHeight: 170, + }, + planTag: { + alignSelf: 'flex-start', + backgroundColor: '#2F2F36', + borderRadius: 14, paddingHorizontal: 12, paddingVertical: 4, + marginBottom: 12, }, - recommendedText: { - color: 'white', - fontSize: 10, - fontWeight: 'bold', - }, - planHeader: { - alignItems: 'center', - marginBottom: 8, - }, - planTitle: { - fontSize: 16, + planTagText: { + color: '#FFFFFF', + fontSize: 11, fontWeight: '600', - color: '#333', }, - lifetimePlanTitle: { - color: '#7B2CBF', + planCardTitle: { + fontSize: 18, + fontWeight: '700', + color: '#241F1F', }, - quarterlyPlanTitle: { - color: '#DF42D0', + planCardPrice: { + fontSize: 28, + fontWeight: '700', + marginTop: 12, }, - weeklyPlanTitle: { - color: '#FF9500', + planCardOriginalPrice: { + fontSize: 13, + color: '#8E8EA1', + textDecorationLine: 'line-through', + marginTop: 4, }, - planPricing: { - alignItems: 'center', - marginBottom: 8, - }, - planPrice: { - fontSize: 24, - fontWeight: 'bold', - color: '#333', - }, - lifetimePlanPrice: { - color: '#7B2CBF', - }, - quarterlyPlanPrice: { - color: '#DF42D0', - }, - weeklyPlanPrice: { - color: '#FF9500', - }, - planSubtitle: { + planCardDescription: { fontSize: 12, - color: '#666', - textAlign: 'center', + color: '#6C6C77', + marginTop: 12, + lineHeight: 17, + }, + tipsContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#FEF4E6', + borderRadius: 14, + paddingHorizontal: 12, + paddingVertical: 10, + marginTop: 16, + }, + tipsText: { + flex: 1, + fontSize: 12, + color: '#9B6200', + marginLeft: 6, lineHeight: 16, }, - purchaseButton: { - backgroundColor: '#DF42D0', - borderRadius: 25, - height: 50, - justifyContent: 'center', + comparisonTable: { + borderRadius: 16, + overflow: 'hidden', + borderWidth: 1, + borderColor: '#ECECF3', + }, + tableRow: { + flexDirection: 'row', alignItems: 'center', - marginBottom: 15, - marginTop: 10, - shadowColor: '#DF42D0', - shadowOffset: { - width: 0, - height: 4, - }, - shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 6, + paddingVertical: 14, + paddingHorizontal: 16, + backgroundColor: '#FFFFFF', }, - disabledButton: { - backgroundColor: '#ccc', - shadowOpacity: 0, - elevation: 0, + tableHeader: { + backgroundColor: '#F8F8FF', }, - purchaseButtonText: { - color: 'white', - fontSize: 18, + tableHeaderText: { + fontSize: 12, + fontWeight: '600', + color: '#575764', + textTransform: 'uppercase', + letterSpacing: 0.4, + }, + tableCellText: { + fontSize: 13, + color: '#3E3E44', + }, + tableTitleCell: { + flex: 1.3, + }, + tableVipCell: { + flex: 0.8, + alignItems: 'center', + }, + tableNormalCell: { + flex: 0.8, + alignItems: 'center', + }, + tableRowAlt: { + backgroundColor: '#FBFBFF', + }, + bottomSection: { + backgroundColor: '#FFFFFF', + borderRadius: 20, + paddingHorizontal: 20, + paddingVertical: 20, + marginBottom: 10, + shadowColor: '#1C1C1E', + shadowOpacity: 0.04, + shadowRadius: 12, + shadowOffset: { width: 0, height: 6 }, + elevation: 1, + }, + noticeBanner: { + backgroundColor: '#FFE7E0', + borderRadius: 12, + paddingVertical: 10, + paddingHorizontal: 16, + marginBottom: 16, + }, + noticeText: { + color: '#D35400', + fontSize: 13, + textAlign: 'center', fontWeight: '600', }, - restoreButton: { - backgroundColor: 'transparent', - borderRadius: 25, - height: 50, + purchaseButton: { + backgroundColor: '#151515', + borderRadius: 28, + height: 52, justifyContent: 'center', alignItems: 'center', - marginBottom: 20, + marginBottom: 16, + }, + disabledButton: { + backgroundColor: '#C6C6C8', + }, + purchaseButtonText: { + color: '#FFFFFF', + fontSize: 18, + fontWeight: '700', + }, + loadingContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + loadingSpinner: { + marginRight: 8, + }, + agreementRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + flexWrap: 'wrap', + marginBottom: 16, + }, + agreementPrefix: { + fontSize: 11, + color: '#666672', + marginHorizontal: 6, + }, + agreementLink: { + fontSize: 11, + color: '#E91E63', + textDecorationLine: 'underline', + fontWeight: '500', + marginHorizontal: 4, + }, + agreementSeparator: { + fontSize: 11, + color: '#A0A0B0', + marginHorizontal: 2, + }, + restoreButton: { + alignSelf: 'center', + paddingVertical: 6, }, restoreButtonText: { - color: '#666', - fontSize: 16, + color: '#6F6F7A', + fontSize: 14, fontWeight: '500', }, disabledRestoreButton: { @@ -1146,49 +1268,7 @@ const styles = StyleSheet.create({ restoreButtonLoader: { marginRight: 8, }, - loadingContainer: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - }, - loadingSpinner: { - marginRight: 8, - }, disabledPlanCard: { opacity: 0.5, }, - agreementRow: { - width: '100%', - marginTop: 6, - borderRadius: 12, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - }, - agreementPrefix: { - fontSize: 10, - color: '#333', - marginRight: 4, - }, - agreementLink: { - fontSize: 10, - color: '#E91E63', - textDecorationLine: 'underline', - fontWeight: '500', - }, - agreementSeparator: { - fontSize: 10, - color: '#666', - marginHorizontal: 2, - }, - tipsContainer: { - borderRadius: 8, - }, - tipsText: { - fontSize: 12, - color: '#666', - lineHeight: 18, - textAlign: 'center', - }, });