import { useState, useEffect, useRef } from 'react'
import {
View,
Text,
StyleSheet,
ScrollView,
Pressable,
StatusBar as RNStatusBar,
Platform,
useWindowDimensions,
ActivityIndicator,
} from 'react-native'
import { StatusBar } from 'expo-status-bar'
import { LinearGradient } from 'expo-linear-gradient'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { useRouter } from 'expo-router'
import { useTranslation } from 'react-i18next'
import { Image } from 'expo-image'
import Carousel from 'react-native-reanimated-carousel'
import Svg, { Path, Defs, LinearGradient as SvgLinearGradient, Stop } from 'react-native-svg'
import { CheckIcon, LeftArrowIcon, OmitIcon, UncheckedIcon, TermsIcon, PrivacyIcon } from '@/components/icon'
import { CheckMarkIcon } from '@/components/icon/checkMark'
import PointsDrawer from '@/components/drawer/PointsDrawer'
import Dropdown from '@/components/ui/dropdown'
import GradientText from '@/components/GradientText'
import { useMembership } from '@/hooks/use-membership'
import { useActivates } from '@/hooks/use-activates'
// 使用唯一 id 的 PointsIcon,避免与其他页面的图标 id 冲突
const MembershipPointsIcon = () => {
const gradientId = 'paint0_linear_membership'
return (
)
}
type PlanType = 'plus' | 'pro' | 'plus-premium'
interface Plan {
id: PlanType
name: string
price: number
recommended?: boolean
points: number // 每月积分
currency: string // 货币类型
featureList: string[] // 功能列表(翻译 key)
}
// 货币符号转换函数
function toUnit(currency: string): string {
switch (currency.toLowerCase()) {
case 'hkd':
return 'HK$'
case 'cny':
return '¥'
default:
return '$'
}
}
export default function MembershipScreen() {
const router = useRouter()
const insets = useSafeAreaInsets()
const { width: screenWidth } = useWindowDimensions()
const [agreed, setAgreed] = useState(true)
const [pointsDrawerVisible, setPointsDrawerVisible] = useState(false)
const [isSubscribing, setIsSubscribing] = useState(false)
const { t } = useTranslation()
// 使用 useMembership hook
const {
creditPlans,
creditBalance,
selectedPlanIndex,
setSelectedPlanIndex,
isLoadingSubscriptions,
isStripePricingLoading,
createSubscription,
upgradeSubscription,
restoreSubscription,
rechargeToken,
activeAuthSubscription,
hasActiveSubscription,
stripePricingData,
} = useMembership()
// 使用 useActivates hook 获取广告数据
const { load: loadActivates, data: activatesData } = useActivates()
// 映射 API 数据到 UI 格式
const plans: Plan[] = creditPlans.map((plan, index) => ({
id: `plan-${index}` as PlanType,
name: plan.name,
price: plan.amountInCents / 100,
recommended: plan.popular,
points: plan.credits,
currency: plan.currency,
featureList: plan.featureList,
}))
const selectedPlan = plans[selectedPlanIndex] || plans[0]
// 下拉菜单选项
const menuOptions = [
{ label: t('membership.terms'), value: 'terms' },
{ label: t('membership.privacy'), value: 'privacy' },
]
// 处理菜单选择
const handleMenuSelect = (value: string) => {
if (value === 'terms') {
router.push('/terms')
} else if (value === 'privacy') {
router.push('/privacy')
}
}
// 轮播图相关 - 使用动态广告数据
const activities = activatesData?.activities || []
const [currentImageIndex, setCurrentImageIndex] = useState(0)
// 加载广告数据
useEffect(() => {
loadActivates()
}, [])
// 处理订阅按钮点击
const handleSubscribe = async () => {
if (!agreed || !selectedPlan) return
const planData = creditPlans[selectedPlanIndex]
if (!planData) return
setIsSubscribing(true)
try {
if (hasActiveSubscription) {
await upgradeSubscription({ credits: planData.credits })
} else if (activeAuthSubscription?.cancelAtPeriodEnd) {
await restoreSubscription()
} else {
const pricingItem = stripePricingData?.pricing_table_items?.[selectedPlanIndex]
if (pricingItem) {
await createSubscription({
priceId: pricingItem.price_id,
productId: pricingItem.product_id,
})
}
}
} finally {
setIsSubscribing(false)
}
}
// 获取当前选中计划的信息
const currentPlan = selectedPlan
// 计算进度条百分比:当前计划积分 / 最高计划积分
const maxPoints = plans.length > 0 ? Math.max(...plans.map(plan => plan.points)) : 1
const progressPercentage = currentPlan?.points ? (currentPlan.points / maxPoints) * 100 : 0
// 加载状态
if (isStripePricingLoading || creditPlans.length === 0) {
return (
)
}
return (
{/* 顶部导航栏 - 固定在顶部 */}
router.back()}
>
setPointsDrawerVisible(true)}
>
{t('membership.myPoints')}
{creditBalance}
handleMenuSelect(value)}
offsetTop={10}
renderTrigger={(selectedOption, isOpen, toggle) => (
)}
dropdownStyle={styles.menuDropdown}
renderOption={(option, isSelected) => (
{option.value === 'terms' ? (
) : (
)}
{option.label}
)}
/>
{/* 只在有广告数据时显示轮播图 */}
{activities.length > 0 && (
<>
(
router.push(item.link as any)}
style={{ width: screenWidth, height: screenWidth / 1.1 }}
>
)}
autoPlay
autoPlayInterval={2000}
loop
onSnapToItem={(index) => setCurrentImageIndex(index)}
enabled={false}
windowSize={1}
mode="parallax"
/>
{activities.map((_, index) => (
))}
>
)}
{/* 订阅计划标题 */}
{t('membership.subscriptionPlan')}
{/* 订阅计划卡片 */}
{plans.map((plan, index) => {
const isSelected = selectedPlanIndex === index
return (
0 && styles.planCardSpacing,
]}
onPress={() => setSelectedPlanIndex(index)}
>
{isSelected ? (
{plan.recommended && (
{t('membership.mostRecommended')}
)}
{plan.name}
{toUnit(plan.currency)}
{plan.price}
{t('membership.perMonth')}
) : (
{plan.recommended && (
{t('membership.mostRecommended')}
)}
{plan.name}
{toUnit(plan.currency)}
{plan.price}
{t('membership.perMonth')}
)}
)
})}
{/* 卡片所属的信息 */}
{/* 积分每月显示 */}
{currentPlan.points.toLocaleString()}
{t('membership.pointsPerMonth')}
{t('membership.pointsAutoRenew')}
{/* 功能列表 */}
{currentPlan.featureList.map((featureKey, index) => {
const translatedText = t(featureKey)
// 如果没有对应的翻译键(翻译结果等于键本身),不展示该项
if (translatedText === featureKey) {
return null
}
return (
{translatedText}
)
})}
{/* 固定在底部的订阅容器 */}
{/* 立即开通按钮 */}
{isLoadingSubscriptions || isStripePricingLoading || isSubscribing ? (
) : (
{t('membership.subscribeNow')}
)}
{/* 协议复选框 */}
setAgreed(!agreed)}
>
{agreed ? : }
{t('membership.agreementText')}{' '}
router.push('/terms')}
>
{t('membership.terms')}
{t('membership.agreementAnd')}
router.push('/privacy')}
>
{t('membership.privacy')}
{/* 积分抽屉 */}
setPointsDrawerVisible(false)}
totalPoints={creditBalance}
subscriptionPoints={0}
topUpPoints={0}
onRecharge={(amount) => rechargeToken(amount)}
/>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#090A0B',
},
contentWrapper: {
flex: 1,
},
scrollView: {
flex: 1,
backgroundColor: '#090A0B',
},
scrollContent: {
backgroundColor: '#090A0B',
flexGrow: 1,
paddingTop: 56, // 为固定的导航栏留出空间
},
header: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12,
paddingTop: 7,
paddingBottom: 7,
zIndex: 100,
backgroundColor: 'transparent',
},
imageContainer: {
width: '100%',
overflow: 'hidden',
marginTop: -56,
position: 'relative',
},
membershipImage: {
width: '100%',
height: '100%',
},
imageGradient: {
position: 'absolute',
bottom: -20,
left: 0,
right: 0,
height: '30%',
zIndex: 10,
elevation: 10,
},
dotsContainer: {
position: 'absolute',
bottom: 40,
left: 16,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 4,
zIndex: 20,
elevation: 20,
},
dot: {
width: 4,
height: 4,
borderRadius: 100,
backgroundColor: '#FFFFFF80',
},
dotActive: {
width: 10,
height: 4,
borderRadius: 100,
backgroundColor: '#FFFFFF',
},
backButton: {
width: 32,
height: 32,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 100,
},
headerSpacer: {
flex: 1,
},
pointsPill: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 100,
backgroundColor: '#2A2A2A80',
},
pointsLabel: {
color: '#F5F5F5',
fontSize: 11,
fontWeight: '500',
},
pointsValue: {
color: '#F5F5F5',
fontSize: 12,
fontWeight: '500',
},
sectionTitle: {
textAlign: 'center',
color: '#F5F5F5',
fontSize: 24,
fontWeight: '900',
lineHeight: 32,
marginBottom: 16,
marginTop: 9,
fontStyle: 'italic',
},
plansContainer: {
flexDirection: 'row',
paddingHorizontal: 16,
marginBottom: 16,
},
planCardWrapper: {
flex: 1,
},
planCardGradient: {
flex: 1,
borderRadius: 12,
padding: 2,
},
planCardWrapperUnselected: {
flex: 1,
borderRadius: 12,
padding: 2,
backgroundColor: 'transparent',
},
planCard: {
flex: 1,
paddingTop: 12,
paddingBottom: 16,
paddingHorizontal: 12,
borderRadius: 12,
backgroundColor: '#16181B',
position: 'relative',
overflow: 'hidden',
},
planCardSpacing: {
marginLeft: 12,
},
recommendedBadge: {
position: 'absolute',
top: -3,
right: -2,
paddingHorizontal: 11,
paddingBottom: 1,
borderRadius: 4,
backgroundColor: '#6851EB',
borderTopRightRadius: 12,
borderBottomRightRadius: 0,
},
recommendedText: {
color: '#F5F5F5',
fontSize: 10,
fontWeight: '500',
},
planName: {
color: '#F5F5F5',
fontSize: 12,
fontWeight: '500',
marginBottom: 14,
},
priceContainer: {
flexDirection: 'row',
alignItems: 'baseline',
},
priceSymbol: {
color: '#F5F5F5',
fontSize: 12,
fontWeight: '500',
marginRight: 2,
},
priceValue: {
color: '#F5F5F5',
fontSize: 24,
fontWeight: '500',
marginRight: 4,
},
priceUnit: {
color: '#ABABAB',
fontSize: 12,
fontWeight: '500',
},
pointsMonthlyContainer: {
marginHorizontal: 16,
backgroundColor: '#191B1F',
paddingHorizontal: 12,
paddingVertical: 12,
borderRadius: 12,
marginBottom: 16,
},
pointsMonthlyCard: {
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 10,
backgroundColor: '#272A30',
marginBottom: 24,
},
pointsMonthlyHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
pointsMonthlyLabelWrapper: {
marginLeft: 8,
},
pointsMonthlyValue: {
color: '#F5F5F5',
fontSize: 13,
fontWeight: '500',
},
pointsMonthlyLabel: {
color: '#F5F5F5',
fontSize: 13,
fontWeight: '500',
},
progressBar: {
width: '100%',
height: 3,
backgroundColor: '#484F5B',
borderRadius: 10,
marginBottom: 10,
overflow: 'hidden',
},
progressFill: {
height: '100%',
borderRadius: 2,
},
pointsMonthlyNote: {
color: '#CCCCCC',
fontSize: 11,
fontWeight: '400',
},
featureItem: {
flexDirection: 'row',
alignItems: 'center',
gap: 16,
marginBottom: 12,
},
featureText: {
flex: 1,
color: '#CCCCCC',
fontSize: 12,
fontWeight: '400',
},
agreementContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginTop: 8,
marginBottom: 16,
paddingVertical: 8,
},
agreementTextWrapper: {
marginLeft: 4,
},
checkbox: {
width: 20,
height: 20,
alignItems: 'center',
justifyContent: 'center',
padding: 4,
},
agreementText: {
color: '#CCCCCC',
fontSize: 11,
fontWeight: '400',
},
agreementLink: {
color: '#CCCCCC',
},
subscribeContainer: {
paddingTop: 8,
alignItems: 'center',
paddingHorizontal: 16,
},
subscribeButtonPressable: {
width: '100%',
height: 48,
borderRadius: 12,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
subscribeButton: {
width: '100%',
paddingVertical: 14,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
subscribeButtonDisabled: {
opacity: 0.5,
},
subscribeButtonText: {
color: '#F5F5F5',
fontSize: 16,
fontWeight: '500',
},
subscribeButtonText1: {
color: '#0D0D0E',
fontSize: 16,
fontWeight: '500',
},
settingsButtonContainer: {
position: 'relative',
},
settingsButton: {
width: 32,
height: 32,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#2A2A2A80',
borderRadius: 100,
},
pointsContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
menuDropdown: {
width: 180,
minWidth: 180,
borderRadius: 12,
backgroundColor: '#2A2A2A80',
paddingVertical: 4,
right: 12,
},
menuOption: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 2,
gap: 8,
},
menuOptionText: {
color: '#F5F5F5',
fontSize: 12,
fontWeight: '400',
flex: 1,
},
loadingContainer: {
justifyContent: 'center',
alignItems: 'center',
},
})