✨ feat: 完整应用重构 - 优化界面架构与用户体验
主要变更: - 重构应用界面:优化首页、登录、认证流程 - 新增用户档案模块:包含完整的档案管理组件 - 优化路由结构:重新组织页面布局和导航 - 改进API集成:新增活动、内容分类等API模块 - 删除冗余组件:清理不必要的文件和依赖 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
150
components/profile/profile-screen.tsx
Normal file
150
components/profile/profile-screen.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, useWindowDimensions, type ImageSourcePropType } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { useAuth } from '@/hooks/use-auth';
|
||||
import { useProfileData } from '@/hooks/use-profile-data';
|
||||
import { createStats, deriveAvatarSource, deriveDisplayName, deriveNumericValue } from '@/utils/profile-data';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { ContentGallery } from './content-gallery';
|
||||
import { ContentTabs } from './content-tabs';
|
||||
import { Divider } from './divider';
|
||||
import { ProfileEditModal } from './profile-edit-modal';
|
||||
import { ProfileEmptyState } from './profile-empty-state';
|
||||
import { ProfileErrorState } from './profile-error-state';
|
||||
import { ProfileHeader } from './profile-header';
|
||||
import { ProfileIdentity } from './profile-identity';
|
||||
import { ProfileLoadingState } from './profile-loading-state';
|
||||
|
||||
type TabKey = 'all' | 'image' | 'video';
|
||||
type BillingMode = 'monthly' | 'lifetime';
|
||||
|
||||
export function ProfileScreen() {
|
||||
const { user } = useAuth();
|
||||
const { width } = useWindowDimensions();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [billingMode, setBillingMode] = useState<BillingMode>('monthly');
|
||||
const [activeTab, setActiveTab] = useState<TabKey>('all');
|
||||
const displayNameFromUser = deriveDisplayName(user) ?? 'prairie_pufferfish_the';
|
||||
const [presentedDisplayName, setPresentedDisplayName] = useState(displayNameFromUser);
|
||||
const [isEditingIdentity, setIsEditingIdentity] = useState(false);
|
||||
|
||||
const {
|
||||
generations,
|
||||
isLoading,
|
||||
error,
|
||||
isRefreshing,
|
||||
isLoadingMore,
|
||||
hasMore,
|
||||
handleRefresh,
|
||||
handleLoadMore,
|
||||
} = useProfileData(activeTab);
|
||||
|
||||
const horizontalPadding = width < 400 ? 20 : 24;
|
||||
const avatarSize = width < 400 ? 74 : 82;
|
||||
const avatarSource = deriveAvatarSource(user);
|
||||
const creditBalance = deriveNumericValue((user as Record<string, unknown>)?.credits);
|
||||
const stats = createStats(user);
|
||||
const [presentedAvatar, setPresentedAvatar] = useState<ImageSourcePropType | undefined>(avatarSource);
|
||||
|
||||
useEffect(() => {
|
||||
setPresentedDisplayName(displayNameFromUser);
|
||||
}, [displayNameFromUser]);
|
||||
|
||||
useEffect(() => {
|
||||
setPresentedAvatar(avatarSource);
|
||||
}, [avatarSource]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={[styles.screen, { backgroundColor: stylesVars.background }]}>
|
||||
{insets.top > 0 && <View style={{ height: insets.top }} />}
|
||||
<ProfileLoadingState />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<View style={[styles.screen, { backgroundColor: stylesVars.background }]}>
|
||||
{insets.top > 0 && <View style={{ height: insets.top }} />}
|
||||
<ProfileErrorState onRetry={handleRefresh} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.screen, { backgroundColor: stylesVars.background }]}>
|
||||
{insets.top > 0 && <View style={{ height: insets.top }} />}
|
||||
|
||||
<View style={{ paddingHorizontal: horizontalPadding }}>
|
||||
<ProfileHeader
|
||||
billingMode={billingMode}
|
||||
onChangeBilling={setBillingMode}
|
||||
credits={creditBalance}
|
||||
/>
|
||||
|
||||
<ProfileIdentity
|
||||
displayName={presentedDisplayName}
|
||||
avatarSource={presentedAvatar}
|
||||
avatarSize={avatarSize}
|
||||
stats={stats}
|
||||
onEdit={() => setIsEditingIdentity(true)}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
<ContentTabs
|
||||
activeTab={activeTab}
|
||||
onChangeTab={setActiveTab}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
</View>
|
||||
|
||||
{generations.length === 0 ? (
|
||||
<ProfileEmptyState activeTab={activeTab} />
|
||||
) : (
|
||||
<View style={[styles.galleryContainer, { paddingHorizontal: horizontalPadding / 2 }]}>
|
||||
<ContentGallery
|
||||
generations={generations}
|
||||
isRefreshing={isRefreshing}
|
||||
onRefresh={handleRefresh}
|
||||
isLoadingMore={isLoadingMore}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={handleLoadMore}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<ProfileEditModal
|
||||
visible={isEditingIdentity}
|
||||
avatarSource={presentedAvatar}
|
||||
initialName={presentedDisplayName}
|
||||
onClose={() => setIsEditingIdentity(false)}
|
||||
onSave={({ name, avatar }) => {
|
||||
setPresentedDisplayName(name);
|
||||
if (avatar) {
|
||||
setPresentedAvatar(avatar);
|
||||
}
|
||||
setIsEditingIdentity(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const stylesVars = {
|
||||
background: '#050505',
|
||||
paddingBottom: 96,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
},
|
||||
galleryContainer: {
|
||||
paddingTop: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default ProfileScreen;
|
||||
Reference in New Issue
Block a user