441 lines
14 KiB
TypeScript
441 lines
14 KiB
TypeScript
import { useState, useEffect } from 'react'
|
||
import {
|
||
View,
|
||
Text,
|
||
StyleSheet,
|
||
ScrollView,
|
||
Dimensions,
|
||
Pressable,
|
||
StatusBar as RNStatusBar,
|
||
} from 'react-native'
|
||
import { StatusBar } from 'expo-status-bar'
|
||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||
import { Image } from 'expo-image'
|
||
import { useRouter } from 'expo-router'
|
||
import { useTranslation } from 'react-i18next'
|
||
import { PointsIcon, SearchIcon, SettingsIcon } from '@/components/icon'
|
||
import EditProfileDrawer from '@/components/drawer/EditProfileDrawer'
|
||
import Dropdown from '@/components/ui/dropdown'
|
||
|
||
const { width: screenWidth } = Dimensions.get('window')
|
||
const GALLERY_GAP = 2
|
||
const GALLERY_HORIZONTAL_PADDING = 0
|
||
// 计算每个卡片的宽度:屏幕宽度 - 左右padding - 2个间距,然后除以3,使用 Math.floor 确保整数像素
|
||
const GALLERY_ITEM_SIZE = Math.floor(
|
||
(screenWidth - GALLERY_HORIZONTAL_PADDING * 2 - GALLERY_GAP * 2) / 3
|
||
)
|
||
// status状态有running pending completed
|
||
const works = [
|
||
{ id: 1, status: 'running' as const, count: 1 },
|
||
{ id: 2, status: 'completed' as const, count: 1 },
|
||
{ id: 3, status: 'completed' as const, count: 1 },
|
||
{ id: 4, status: 'completed' as const, count: 2 },
|
||
{ id: 5, status: 'pending' as const, count: 1 },
|
||
{ id: 6, status: 'pending' as const, count: 1 },
|
||
{ id: 7, status: 'pending' as const, count: 1 },
|
||
{ id: 8, status: 'pending' as const, count: 1 },
|
||
{ id: 9, status: 'completed' as const, count: 1 },
|
||
{ id: 10, status: 'completed' as const, count: 1 },
|
||
{ id: 11, status: 'completed' as const, count: 1 },
|
||
{ id: 12, status: 'completed' as const, count: 1 },
|
||
{ id: 13, status: 'completed' as const, count: 1 },
|
||
{ id: 14, status: 'completed' as const, count: 1 },
|
||
{ id: 15, status: 'completed' as const, count: 1 },
|
||
{ id: 16, status: 'completed' as const, count: 1 },
|
||
{ id: 17, status: 'completed' as const, count: 1 },
|
||
|
||
]
|
||
|
||
export default function My() {
|
||
const router = useRouter()
|
||
const { t, i18n } = useTranslation()
|
||
const [editDrawerVisible, setEditDrawerVisible] = useState(false)
|
||
const [profileName, setProfileName] = useState('乔乔乔乔')
|
||
|
||
// 处理设置菜单选择
|
||
const handleSettingsSelect = (value: string) => {
|
||
if (value === 'changePassword') {
|
||
router.push('/changePassword' as any)
|
||
} else if (value === 'language') {
|
||
// 切换语言
|
||
const newLang = i18n.language === 'zh-CN' ? 'en-US' : 'zh-CN'
|
||
i18n.changeLanguage(newLang)
|
||
}
|
||
}
|
||
|
||
// 设置菜单选项
|
||
const getLanguageLabel = () => {
|
||
if (i18n.language === 'zh-CN') {
|
||
return t('my.languageSwitch')
|
||
} else {
|
||
return t('my.languageSwitchEn')
|
||
}
|
||
}
|
||
|
||
const settingsOptions = [
|
||
{ label: t('my.changePassword'), value: 'changePassword' },
|
||
{ label: getLanguageLabel(), value: 'language' },
|
||
]
|
||
|
||
return (
|
||
<SafeAreaView style={styles.container} edges={['top']}>
|
||
<StatusBar style="light" />
|
||
<RNStatusBar barStyle="light-content" />
|
||
|
||
{/* 顶部积分与设置 */}
|
||
<View style={styles.topBar}>
|
||
<Pressable
|
||
style={styles.pointsPill}
|
||
onPress={() => router.push('/membership' as any)}
|
||
>
|
||
<PointsIcon />
|
||
<Text style={styles.pointsPillText}>60</Text>
|
||
</Pressable>
|
||
<Dropdown
|
||
options={settingsOptions}
|
||
onSelect={(value) => handleSettingsSelect(value)}
|
||
renderTrigger={(selectedOption, isOpen, toggle) => (
|
||
<Pressable onPress={toggle}>
|
||
<SettingsIcon />
|
||
</Pressable>
|
||
)}
|
||
dropdownStyle={{
|
||
minWidth: 160,
|
||
right: 10,
|
||
backgroundColor: '#2A2A2A80',
|
||
}}
|
||
/>
|
||
</View>
|
||
|
||
{/* 个人信息区 */}
|
||
<View style={styles.profileSection}>
|
||
<Image
|
||
source={require('@/assets/images/icon.png')}
|
||
style={styles.avatar}
|
||
contentFit="cover"
|
||
/>
|
||
<View style={styles.profileInfo}>
|
||
<Text style={styles.profileName}>{profileName}</Text>
|
||
<Text style={styles.profileSubTitle}>ID 12345678</Text>
|
||
</View>
|
||
<Pressable
|
||
style={styles.editButton}
|
||
onPress={() => setEditDrawerVisible(true)}
|
||
>
|
||
<Text style={styles.editButtonText}>{t('my.editProfile')}</Text>
|
||
</Pressable>
|
||
</View>
|
||
|
||
{/* "生成作品" 标题行 */}
|
||
<View style={styles.sectionHeader}>
|
||
<Text style={styles.sectionTitle}>{t('my.generatedWorks')}</Text>
|
||
<Pressable
|
||
style={styles.sectionMoreButton}
|
||
onPress={() => router.push('/worksList' as any)}
|
||
>
|
||
<SearchIcon />
|
||
</Pressable>
|
||
</View>
|
||
|
||
{/* 作品九宫格 */}
|
||
<ScrollView
|
||
style={styles.scrollView}
|
||
contentContainerStyle={styles.scrollContent}
|
||
showsVerticalScrollIndicator={false}
|
||
>
|
||
<View style={styles.galleryGrid}>
|
||
{works.map((item, index) => (
|
||
<Pressable
|
||
key={item.id}
|
||
style={[
|
||
styles.galleryItem,
|
||
// 每行的前两个item有右边距,第三个没有
|
||
index % 3 !== 2 && styles.galleryItemMarginRight,
|
||
// 所有item都有下边距(最后一行也会有,但影响不大)
|
||
styles.galleryItemMarginBottom,
|
||
]}
|
||
onPress={() => {
|
||
// 只有已完成的作品才能点击进入详情页
|
||
if (item.status === 'completed') {
|
||
router.push({
|
||
pathname: '/generationRecord' as any,
|
||
params: { id: item.id.toString() },
|
||
})
|
||
}
|
||
}}
|
||
disabled={item.status !== 'completed'}
|
||
>
|
||
<Image
|
||
source={require('@/assets/images/membership.png')}
|
||
style={styles.galleryImage}
|
||
contentFit="cover"
|
||
/>
|
||
|
||
{/* 生成中遮罩 */}
|
||
{item.status != 'completed' && (
|
||
<View style={styles.generatingOverlay} />
|
||
)}
|
||
|
||
{/* 右上角作品数量角标 */}
|
||
{item.status === 'completed'&&<View style={styles.counterBadge}>
|
||
<Text style={styles.counterText}>
|
||
{item.count}
|
||
</Text>
|
||
</View>}
|
||
|
||
{/* "生成中"角标:覆盖在图片左下角 */}
|
||
{item.status === 'running' && (
|
||
<View style={styles.generatingBadge}>
|
||
<Text style={styles.generatingBadgeText}>
|
||
{t('my.generating')}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
{item.status === 'pending' && (
|
||
<View style={styles.generatingBadge}>
|
||
<Text style={styles.generatingBadgeText}>
|
||
{t('my.queuing')}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
</Pressable>
|
||
))}
|
||
</View>
|
||
</ScrollView>
|
||
|
||
{/* 编辑资料抽屉 */}
|
||
<EditProfileDrawer
|
||
visible={editDrawerVisible}
|
||
onClose={() => setEditDrawerVisible(false)}
|
||
initialName={profileName}
|
||
onSave={(name) => setProfileName(name)}
|
||
/>
|
||
</SafeAreaView>
|
||
)
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: '#090A0B',
|
||
},
|
||
topBar: {
|
||
paddingHorizontal: GALLERY_HORIZONTAL_PADDING,
|
||
paddingTop: 19,
|
||
paddingRight: 16,
|
||
flexDirection: 'row',
|
||
justifyContent: 'flex-end',
|
||
backgroundColor: '#090A0B',
|
||
gap: 12,
|
||
},
|
||
pointsPill: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 1,
|
||
paddingHorizontal: 10,
|
||
paddingVertical: 4,
|
||
borderRadius: 100,
|
||
backgroundColor: '#1C1E22',
|
||
},
|
||
pointsPillText: {
|
||
color: '#FFCF00',
|
||
fontSize: 12,
|
||
fontWeight: '600',
|
||
},
|
||
header: {
|
||
backgroundColor: '#000000',
|
||
paddingTop: 8,
|
||
paddingBottom: 12,
|
||
},
|
||
statusBar: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
paddingHorizontal: 16,
|
||
paddingBottom: 8,
|
||
},
|
||
time: {
|
||
color: '#FFFFFF',
|
||
fontSize: 14,
|
||
fontWeight: '600',
|
||
},
|
||
statusIcons: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 4,
|
||
},
|
||
signalBars: {
|
||
width: 18,
|
||
height: 12,
|
||
backgroundColor: '#FFFFFF',
|
||
borderRadius: 2,
|
||
},
|
||
wifiIcon: {
|
||
width: 16,
|
||
height: 12,
|
||
backgroundColor: '#FFFFFF',
|
||
borderRadius: 2,
|
||
},
|
||
batteryIcon: {
|
||
width: 24,
|
||
height: 12,
|
||
backgroundColor: '#FFFFFF',
|
||
borderRadius: 2,
|
||
},
|
||
titleBar: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
paddingHorizontal: 16,
|
||
},
|
||
appTitle: {
|
||
color: '#FFFFFF',
|
||
fontSize: 18,
|
||
fontWeight: '700',
|
||
},
|
||
headerRight: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 12,
|
||
},
|
||
pointsContainer: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 4,
|
||
},
|
||
pointsText: {
|
||
color: '#FFFFFF',
|
||
fontSize: 14,
|
||
fontWeight: '600',
|
||
},
|
||
searchButton: {
|
||
padding: 4,
|
||
},
|
||
scrollView: {
|
||
flex: 1,
|
||
backgroundColor: '#090A0B',
|
||
},
|
||
scrollContent: {
|
||
backgroundColor: '#090A0B',
|
||
paddingHorizontal: GALLERY_HORIZONTAL_PADDING,
|
||
},
|
||
profileSection: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
paddingLeft: 16,
|
||
paddingRight: 16,
|
||
marginTop: 20,
|
||
marginBottom: 32,
|
||
backgroundColor: '#090A0B',
|
||
},
|
||
avatar: {
|
||
width: 64,
|
||
height: 64,
|
||
borderRadius: 32,
|
||
overflow: 'hidden',
|
||
marginRight: 16,
|
||
},
|
||
profileInfo: {
|
||
flex: 1,
|
||
},
|
||
profileName: {
|
||
color: '#F5F5F5',
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
marginBottom: 4,
|
||
},
|
||
profileSubTitle: {
|
||
color: '#FFFFFF',
|
||
fontSize: 12,
|
||
opacity: 0.7,
|
||
},
|
||
editButton: {
|
||
paddingHorizontal: 8, //左右各留 8 像素的内边距
|
||
paddingVertical: 6, //上下各留 6 像素的内边距
|
||
borderRadius: 6,
|
||
backgroundColor: '#1C1E22',
|
||
},
|
||
editButtonText: {
|
||
color: '#FFFFFF',
|
||
fontSize: 12,
|
||
},
|
||
sectionHeader: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
marginBottom: 12,
|
||
marginHorizontal: 12,
|
||
backgroundColor: '#090A0B',
|
||
},
|
||
sectionTitle: {
|
||
color: '#FFFFFF',
|
||
fontSize: 14,
|
||
fontWeight: '600',
|
||
},
|
||
sectionMoreButton: {
|
||
width: 20,
|
||
height: 20,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
sectionMoreIcon: {
|
||
width: 10,
|
||
height: 10,
|
||
borderRadius: 5,
|
||
backgroundColor: '#FFFFFF33',
|
||
},
|
||
galleryGrid: {
|
||
flexDirection: 'row',
|
||
flexWrap: 'wrap',
|
||
marginBottom: 10,
|
||
},
|
||
galleryItem: {
|
||
width: GALLERY_ITEM_SIZE,
|
||
// 使用等比例 1:1,保证容器永远是正方形
|
||
aspectRatio: 1,
|
||
overflow: 'hidden',
|
||
backgroundColor: '#1C1E22',
|
||
position: 'relative',
|
||
},
|
||
galleryItemMarginRight: {
|
||
marginRight: GALLERY_GAP,
|
||
},
|
||
galleryItemMarginBottom: {
|
||
marginBottom: GALLERY_GAP,
|
||
},
|
||
galleryImage: {
|
||
width: '100%',
|
||
// 高度由 aspectRatio 决定,避免拉伸
|
||
height: undefined,
|
||
aspectRatio: 1,
|
||
},
|
||
generatingOverlay: {
|
||
...StyleSheet.absoluteFillObject,
|
||
backgroundColor: '#00000080',
|
||
},
|
||
counterBadge: {
|
||
position: 'absolute',
|
||
right: 8,
|
||
bottom: 8,
|
||
paddingHorizontal: 10,
|
||
paddingVertical: 1,
|
||
borderRadius: 6,
|
||
backgroundColor: '#16181B1A',
|
||
},
|
||
counterText: {
|
||
color: '#FFFFFF',
|
||
fontSize: 10,
|
||
fontWeight: '600',
|
||
},
|
||
generatingBadge: {
|
||
position: 'absolute',
|
||
left: 8,
|
||
bottom: 8,
|
||
},
|
||
generatingBadgeText: {
|
||
color: '#F5F5F5',
|
||
fontSize: 9,
|
||
fontWeight: '500',
|
||
},
|
||
})
|