Initial commit: expo-popcore-app

This commit is contained in:
imeepos
2025-12-25 16:25:55 +08:00
commit 02d0c807cd
160 changed files with 39560 additions and 0 deletions

440
app/(tabs)/my.tsx Normal file
View File

@@ -0,0 +1,440 @@
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',
},
})