449 lines
14 KiB
TypeScript
449 lines
14 KiB
TypeScript
import { useState, useRef, useMemo, useCallback, useEffect } from 'react'
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
Pressable,
|
|
useWindowDimensions,
|
|
} from 'react-native'
|
|
import { LinearGradient } from 'expo-linear-gradient'
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
import { useTranslation } from 'react-i18next'
|
|
import BottomSheet, { BottomSheetView, BottomSheetBackdrop, BottomSheetScrollView } from '@gorhom/bottom-sheet'
|
|
import { CloseIcon } from '@/components/icon'
|
|
import TopUpDrawer, { TopUpOption } from '@/components/drawer/TopUpDrawer'
|
|
|
|
export type PointsTabType = 'all' | 'consume' | 'obtain'
|
|
|
|
export interface PointsTransaction {
|
|
id: string
|
|
title: string
|
|
date: string
|
|
points: number // 正数表示获得,负数表示消耗
|
|
}
|
|
|
|
export interface PointsDrawerProps {
|
|
/**
|
|
* 是否显示抽屉
|
|
*/
|
|
visible: boolean
|
|
/**
|
|
* 关闭回调
|
|
*/
|
|
onClose: () => void
|
|
/**
|
|
* 当前积分总额
|
|
*/
|
|
totalPoints?: number
|
|
/**
|
|
* 订阅积分
|
|
*/
|
|
subscriptionPoints?: number
|
|
/**
|
|
* 额外充值积分
|
|
*/
|
|
topUpPoints?: number
|
|
/**
|
|
* 交易记录列表
|
|
*/
|
|
transactions?: PointsTransaction[]
|
|
}
|
|
|
|
export default function PointsDrawer({
|
|
visible,
|
|
onClose,
|
|
totalPoints = 60,
|
|
subscriptionPoints = 0,
|
|
topUpPoints = 0,
|
|
transactions = [],
|
|
}: PointsDrawerProps) {
|
|
const { t } = useTranslation()
|
|
const { height: screenHeight } = useWindowDimensions()
|
|
const insets = useSafeAreaInsets()
|
|
const bottomSheetRef = useRef<BottomSheet>(null)
|
|
const [pointsTab, setPointsTab] = useState<PointsTabType>('all')
|
|
const [topUpDrawerVisible, setTopUpDrawerVisible] = useState(false)
|
|
|
|
const snapPoints = useMemo(() => [screenHeight * 0.85], [screenHeight])
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
bottomSheetRef.current?.expand()
|
|
} else {
|
|
bottomSheetRef.current?.close()
|
|
}
|
|
}, [visible])
|
|
|
|
const handleSheetChanges = useCallback((index: number) => {
|
|
if (index === -1) {
|
|
onClose()
|
|
}
|
|
}, [onClose])
|
|
|
|
const renderBackdrop = useCallback(
|
|
(props: any) => (
|
|
<BottomSheetBackdrop
|
|
{...props}
|
|
disappearsOnIndex={-1}
|
|
appearsOnIndex={0}
|
|
opacity={0.5}
|
|
/>
|
|
),
|
|
[]
|
|
)
|
|
|
|
// 标签页配置
|
|
const tabOptions: Array<{ value: PointsTabType; label: string }> = [
|
|
{ value: 'all', label: t('pointsDrawer.all') },
|
|
{ value: 'consume', label: t('pointsDrawer.consume') },
|
|
{ value: 'obtain', label: t('pointsDrawer.obtain') },
|
|
]
|
|
|
|
// 根据标签页过滤交易记录
|
|
const filteredTransactions = transactions.filter((transaction) => {
|
|
if (pointsTab === 'all') return true
|
|
if (pointsTab === 'consume') return transaction.points < 0
|
|
if (pointsTab === 'obtain') return transaction.points > 0
|
|
return true
|
|
})
|
|
|
|
// 如果没有提供交易记录,使用示例数据
|
|
const displayTransactions =
|
|
filteredTransactions.length > 0
|
|
? filteredTransactions
|
|
: [
|
|
{
|
|
id: '1',
|
|
title: t('pointsDrawer.dailyFreePoints'),
|
|
date: '2025年11月28日 10:33',
|
|
points: 60,
|
|
},
|
|
...Array.from({ length: 60 }, (_, i) => ({
|
|
id: `example-${i + 2}`,
|
|
title: t('pointsDrawer.dailyFreePoints'),
|
|
date: '2025年11月28日 10:33',
|
|
points: -60,
|
|
})),
|
|
]
|
|
|
|
return (
|
|
<BottomSheet
|
|
ref={bottomSheetRef}
|
|
index={visible ? 0 : -1}
|
|
snapPoints={snapPoints}
|
|
onChange={handleSheetChanges}
|
|
enablePanDownToClose
|
|
backgroundStyle={styles.bottomSheetBackground}
|
|
handleComponent={null}
|
|
backdropComponent={renderBackdrop}
|
|
>
|
|
<BottomSheetView style={styles.container}>
|
|
{/* 顶部标题栏 */}
|
|
<View style={styles.header}>
|
|
<Text></Text>
|
|
<Pressable
|
|
style={styles.closeButton}
|
|
onPress={onClose}
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
>
|
|
<CloseIcon />
|
|
</Pressable>
|
|
</View>
|
|
<View style={styles.titleContainer}>
|
|
<Text style={styles.title}>{t('pointsDrawer.title')}</Text>
|
|
{/* 积分总额 */}
|
|
<View style={styles.balance}>
|
|
<Text style={styles.balanceValue}>{totalPoints}</Text>
|
|
</View>
|
|
|
|
{/* 积分类型细分 */}
|
|
<View style={styles.breakdown}>
|
|
<Text style={styles.breakdownText}>{t('pointsDrawer.subscriptionPoints')}
|
|
<Text style={styles.breakdownTextValue}>{subscriptionPoints}</Text>
|
|
</Text>
|
|
<View style={styles.breakdownTextSeparator} />
|
|
<Text style={styles.breakdownText}>{t('pointsDrawer.topUpPoints')}
|
|
<Text style={styles.breakdownTextValue}>{topUpPoints}</Text>
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 标签页 */}
|
|
<View style={styles.tabs}>
|
|
{tabOptions.map((tab) => {
|
|
const isActive = pointsTab === tab.value
|
|
return (
|
|
<Pressable
|
|
key={tab.value}
|
|
style={[styles.tab]}
|
|
onPress={() => setPointsTab(tab.value)}
|
|
>
|
|
<View style={styles.tabContent}>
|
|
{isActive && (
|
|
<LinearGradient
|
|
colors={['#FF9966', '#FF6699', '#9966FF']}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 0 }}
|
|
style={styles.tabGradient}
|
|
/>
|
|
)}
|
|
<Text style={[styles.tabText, isActive && styles.tabTextActive]}>{tab.label}</Text>
|
|
</View>
|
|
</Pressable>
|
|
)
|
|
})}
|
|
</View>
|
|
|
|
{/* 交易历史列表 */}
|
|
<BottomSheetScrollView
|
|
style={styles.list}
|
|
contentContainerStyle={styles.listContent}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{displayTransactions.map((transaction) => (
|
|
<View key={transaction.id} style={styles.item}>
|
|
<View style={styles.itemLeft}>
|
|
<Text style={styles.itemTitle}>{transaction.title}</Text>
|
|
<Text style={styles.itemDate}>{transaction.date}</Text>
|
|
</View>
|
|
<Text
|
|
style={[
|
|
styles.itemPoints,
|
|
transaction.points < 0 && styles.itemPointsNegative,
|
|
]}
|
|
>
|
|
{transaction.points > 0 ? '+' : ''}
|
|
{transaction.points}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</BottomSheetScrollView>
|
|
|
|
{/* 底部按钮 */}
|
|
<View style={[styles.footer, { paddingBottom: Math.max(insets.bottom, 16) }]}>
|
|
<Pressable
|
|
style={styles.subscribeButton}
|
|
onPress={() => {
|
|
onClose()
|
|
}}
|
|
>
|
|
<LinearGradient
|
|
colors={['#FF9966', '#FF6699', '#9966FF']}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 0 }}
|
|
style={styles.subscribeButtonGradient}
|
|
|
|
>
|
|
<Text style={styles.subscribeButtonText}>{t('pointsDrawer.subscribeForPoints')}</Text>
|
|
</LinearGradient>
|
|
</Pressable>
|
|
<Pressable
|
|
style={styles.topUpButton}
|
|
onPress={() => {
|
|
setTopUpDrawerVisible(true)
|
|
}}
|
|
>
|
|
<Text style={styles.topUpButtonText}>{t('pointsDrawer.topUpPointsButton')}</Text>
|
|
</Pressable>
|
|
</View>
|
|
</BottomSheetView>
|
|
|
|
{/* 充值抽屉 */}
|
|
<TopUpDrawer
|
|
visible={topUpDrawerVisible}
|
|
onClose={() => setTopUpDrawerVisible(false)}
|
|
onNavigate={() => {
|
|
setTopUpDrawerVisible(false)
|
|
onClose()
|
|
}}
|
|
requiredPoints={100}
|
|
remainingPoints={totalPoints}
|
|
topUpTitle={t('topUp.title')}
|
|
onConfirm={(option: TopUpOption) => {
|
|
// 处理充值确认逻辑
|
|
console.log('确认充值:', option)
|
|
setTopUpDrawerVisible(false)
|
|
}}
|
|
/>
|
|
</BottomSheet>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
bottomSheetBackground: {
|
|
backgroundColor: '#090A0B',
|
|
borderTopLeftRadius: 24,
|
|
borderTopRightRadius: 24,
|
|
},
|
|
handleIndicator: {
|
|
backgroundColor: '#666666',
|
|
},
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#090A0B',
|
|
borderTopLeftRadius: 20,
|
|
borderTopRightRadius: 20,
|
|
overflow: 'hidden',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingTop: 12,
|
|
marginRight: 12,
|
|
},
|
|
titleContainer: {
|
|
paddingLeft: 20,
|
|
marginTop: -4,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#3A3A3A',
|
|
},
|
|
title: {
|
|
color: '#F5F5F5',
|
|
fontSize: 12,
|
|
fontWeight: '500',
|
|
},
|
|
closeButton: {
|
|
width: 24,
|
|
height: 24,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
balance: {
|
|
marginBottom: 13,
|
|
},
|
|
balanceValue: {
|
|
color: '#F5F5F5',
|
|
fontSize: 40,
|
|
fontWeight: '500',
|
|
},
|
|
breakdown: {
|
|
flexDirection: 'row',
|
|
gap: 16,
|
|
marginBottom: 24,
|
|
},
|
|
breakdownText: {
|
|
color: '#ABABAB',
|
|
fontSize: 12,
|
|
fontWeight: '400',
|
|
},
|
|
breakdownTextValue: {
|
|
color: '#F5F5F5',
|
|
fontSize: 12,
|
|
fontWeight: '500',
|
|
marginLeft: 6,
|
|
},
|
|
breakdownTextSeparator: {
|
|
width: 1,
|
|
height: 14,
|
|
backgroundColor: '#3A3A3A',
|
|
marginTop: 2,
|
|
},
|
|
tabs: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingHorizontal: 16,
|
|
marginTop:20,
|
|
marginBottom:24,
|
|
},
|
|
tab: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
tabContent: {
|
|
position: 'relative',
|
|
alignSelf: 'center',
|
|
},
|
|
tabGradient: {
|
|
position: 'absolute',
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
height: 10,
|
|
backgroundColor: '#FF9966',
|
|
},
|
|
tabText: {
|
|
color: '#ABABAB',
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
},
|
|
tabTextActive: {
|
|
color: '#F5F5F5',
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
},
|
|
list: {
|
|
height: 400,
|
|
},
|
|
listContent: {
|
|
paddingBottom: 16,
|
|
paddingHorizontal: 16,
|
|
},
|
|
item: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
paddingVertical: 16,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#1C1E20',
|
|
},
|
|
itemLeft: {
|
|
flex: 1,
|
|
},
|
|
itemTitle: {
|
|
color: '#F5F5F5',
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
marginBottom: 4,
|
|
},
|
|
itemDate: {
|
|
color: '#ABABAB',
|
|
fontSize: 12,
|
|
fontWeight: '400',
|
|
},
|
|
itemPoints: {
|
|
color: '#4CAF50',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
itemPointsNegative: {
|
|
color: '#F5F5F5',
|
|
},
|
|
footer: {
|
|
paddingTop: 20,
|
|
paddingHorizontal: 16,
|
|
gap: 4,
|
|
},
|
|
subscribeButton: {
|
|
width: '100%',
|
|
height: 48,
|
|
borderRadius: 12,
|
|
overflow: 'hidden',
|
|
},
|
|
subscribeButtonGradient: {
|
|
width: '100%',
|
|
height: 48,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: 12,
|
|
},
|
|
subscribeButtonText: {
|
|
color: '#F5F5F5',
|
|
fontSize: 16,
|
|
fontWeight: '500',
|
|
},
|
|
topUpButton: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 12,
|
|
},
|
|
topUpButtonText: {
|
|
color: '#F5F5F5',
|
|
fontSize: 12,
|
|
fontWeight: '400',
|
|
},
|
|
})
|
|
|