This commit is contained in:
imeepos
2026-01-28 14:39:46 +08:00
parent b46ad76161
commit ca63868282
4 changed files with 733 additions and 64 deletions

View File

@@ -8,6 +8,7 @@ import {
StatusBar as RNStatusBar,
Platform,
useWindowDimensions,
ActivityIndicator,
} from 'react-native'
import { StatusBar } from 'expo-status-bar'
import { LinearGradient } from 'expo-linear-gradient'
@@ -22,7 +23,7 @@ import { CheckMarkIcon } from '@/components/icon/checkMark'
import PointsDrawer from '@/components/drawer/PointsDrawer'
import Dropdown from '@/components/ui/dropdown'
import GradientText from '@/components/GradientText'
import { useUserBalance } from '@/hooks/use-user-balance'
import { useMembership } from '@/hooks/use-membership'
// 使用唯一 id 的 PointsIcon避免与其他页面的图标 id 冲突
const MembershipPointsIcon = () => {
@@ -65,63 +66,53 @@ export default function MembershipScreen() {
const router = useRouter()
const insets = useSafeAreaInsets()
const { width: screenWidth } = useWindowDimensions()
const [selectedPlan, setSelectedPlan] = useState<PlanType>('pro')
const [agreed, setAgreed] = useState(false)
const [pointsDrawerVisible, setPointsDrawerVisible] = useState(false)
const { t } = useTranslation()
// 获取积分余额
const { balance } = useUserBalance()
// 订阅计划数据(使用国际化)
const plans: Plan[] = [
{
id: 'plus',
name: 'Plus',
price: 9,
points: 750,
features: [
t('membership.features.points750'),
t('membership.features.textToVideo'),
t('membership.features.superClearImage'),
t('membership.features.superDiscount'),
]
},
{
id: 'pro',
name: 'Pro',
price: 29,
recommended: true,
points: 1080,
features: [
t('membership.features.points1080'),
t('membership.features.textToVideo'),
t('membership.features.superClearImage'),
t('membership.features.superDiscount'),
t('membership.features.sora2ProTemplate'),
t('membership.features.removeWatermark'),
t('membership.features.allTemplates'),
]
},
{
id: 'plus-premium',
name: 'Plus',
price: 49,
points: 1500,
features: [
t('membership.features.points1500'),
t('membership.features.textToVideo'),
t('membership.features.superClearImage'),
t('membership.features.superDiscount'),
// 使用 useMembership hook
const {
creditPlans,
creditBalance,
selectedPlanIndex,
setSelectedPlanIndex,
isLoadingSubscriptions,
isStripePricingLoading,
createSubscription,
upgradeSubscription,
restoreSubscription,
rechargeToken,
activeAuthSubscription,
hasActiveSubscription,
stripePricingData,
} = useMembership()
// 映射 API 数据到 UI 格式
const plans: Plan[] = creditPlans.map((plan, index) => ({
id: `plan-${index}` as PlanType,
name: index === 0 ? 'Plus' : index === 1 ? 'Pro' : 'Plus',
price: plan.amountInCents / 100,
recommended: plan.popular,
points: plan.credits,
features: [
t('membership.features.points750'),
t('membership.features.textToVideo'),
t('membership.features.superClearImage'),
t('membership.features.superDiscount'),
...(index > 0 ? [
t('membership.features.sora2ProTemplate'),
t('membership.features.removeWatermark'),
t('membership.features.allTemplates'),
] : []),
...(index > 1 ? [
t('membership.features.higherQuality'),
t('membership.features.prioritySupport'),
]
},
]
] : []),
],
}))
const selectedPlan = plans[selectedPlanIndex] || plans[0]
// 下拉菜单选项
const menuOptions = [
@@ -146,18 +137,52 @@ export default function MembershipScreen() {
]
const [currentImageIndex, setCurrentImageIndex] = useState(0)
// 处理订阅按钮点击
const handleSubscribe = () => {
if (!agreed || !selectedPlan) return
const planData = creditPlans[selectedPlanIndex]
if (!planData) return
if (hasActiveSubscription) {
upgradeSubscription({ credits: planData.credits })
} else if (activeAuthSubscription?.cancelAtPeriodEnd) {
restoreSubscription()
} else {
const pricingItem = stripePricingData?.pricing_table_items?.[selectedPlanIndex]
if (pricingItem) {
createSubscription({
priceId: pricingItem.price_id,
productId: pricingItem.product_id,
})
}
}
}
// 获取当前选中计划的信息
const currentPlan = plans.find(plan => plan.id === selectedPlan) || plans[1]
const currentPlan = selectedPlan
// 计算进度条百分比:当前计划积分 / 最高计划积分
const maxPoints = Math.max(...plans.map(plan => plan.points))
const progressPercentage = (currentPlan.points / maxPoints) * 100
const progressPercentage = (currentPlan?.points / maxPoints) * 100
// 加载状态
if (isStripePricingLoading || creditPlans.length === 0) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<StatusBar style="light" />
<View style={[styles.container, styles.loadingContainer]}>
<ActivityIndicator size="large" color="#FF9966" />
</View>
</SafeAreaView>
)
}
return (
<SafeAreaView style={styles.container} edges={['top']}>
<StatusBar style="light" />
<RNStatusBar
barStyle="light-content"
<RNStatusBar
barStyle="light-content"
backgroundColor="#090A0B"
translucent={Platform.OS === 'android'}
/>
@@ -172,13 +197,13 @@ export default function MembershipScreen() {
</Pressable>
<View style={styles.headerSpacer} />
<View style={styles.pointsContainer}>
<Pressable
<Pressable
style={styles.pointsPill}
onPress={() => setPointsDrawerVisible(true)}
>
<Text style={styles.pointsLabel}>{t('membership.myPoints')}</Text>
<MembershipPointsIcon />
<Text style={styles.pointsValue}>{balance}</Text>
<Text style={styles.pointsValue}>{creditBalance}</Text>
</Pressable>
<View style={styles.settingsButtonContainer}>
<Dropdown
@@ -253,7 +278,7 @@ export default function MembershipScreen() {
{/* 订阅计划卡片 */}
<View style={styles.plansContainer}>
{plans.map((plan, index) => {
const isSelected = selectedPlan === plan.id
const isSelected = selectedPlanIndex === index
return (
<Pressable
key={plan.id}
@@ -261,7 +286,7 @@ export default function MembershipScreen() {
styles.planCardWrapper,
index > 0 && styles.planCardSpacing,
]}
onPress={() => setSelectedPlan(plan.id)}
onPress={() => setSelectedPlanIndex(index)}
>
{isSelected ? (
<LinearGradient
@@ -365,15 +390,16 @@ export default function MembershipScreen() {
<View style={[styles.subscribeContainer, { paddingBottom: Math.max(insets.bottom, 3) }]}>
{/* 立即开通按钮 */}
<Pressable
disabled={!agreed}
disabled={!agreed || isLoadingSubscriptions || isStripePricingLoading}
style={styles.subscribeButtonPressable}
onPress={handleSubscribe}
>
<LinearGradient
colors={currentPlan.recommended
colors={currentPlan.recommended
? ['#9966FF', '#FF6699', '#FF9966']
: ['#FFE7F8', '#C6A7FA', '#ADBCFF', '#E6E3FF']
}
locations={currentPlan.recommended
locations={currentPlan.recommended
? [0.0015, 0.4985, 0.9956]
: [0, 0.33, 0.66, 1]
}
@@ -381,12 +407,14 @@ export default function MembershipScreen() {
end={{ x: 0, y: 0 }}
style={[
styles.subscribeButton,
!agreed && styles.subscribeButtonDisabled,
(!agreed || isLoadingSubscriptions || isStripePricingLoading) && styles.subscribeButtonDisabled,
]}
>
{isLoadingSubscriptions || isStripePricingLoading ? (
<ActivityIndicator color="#F5F5F5" />
) : (
<Text style={currentPlan.recommended ? styles.subscribeButtonText : styles.subscribeButtonText1}>{t('membership.subscribeNow')}</Text>
)}
</LinearGradient>
</Pressable>
{/* 协议复选框 */}
@@ -423,9 +451,10 @@ export default function MembershipScreen() {
<PointsDrawer
visible={pointsDrawerVisible}
onClose={() => setPointsDrawerVisible(false)}
totalPoints={balance}
totalPoints={creditBalance}
subscriptionPoints={0}
topUpPoints={0}
onRecharge={(amount) => rechargeToken(amount)}
/>
</SafeAreaView>
)
@@ -770,5 +799,9 @@ const styles = StyleSheet.create({
fontWeight: '400',
flex: 1,
},
loadingContainer: {
justifyContent: 'center',
alignItems: 'center',
},
})