174 lines
5.1 KiB
TypeScript
174 lines
5.1 KiB
TypeScript
import React, { useMemo, useState } from 'react';
|
|
import { StyleSheet, View, useWindowDimensions, type ImageSourcePropType } from 'react-native';
|
|
|
|
import { PageLayout } from '@/components/bestai/layout';
|
|
import { useAuth } from '@/hooks/use-auth';
|
|
import { useBalance } from '@/hooks/use-balance';
|
|
import { useProfileData } from '@/hooks/use-profile-data';
|
|
import { PROFILE_THEME } from '@/theme/profile';
|
|
import { createStats, deriveAvatarSource, deriveDisplayName } from '@/utils/profile-data';
|
|
import { ContentGallery } from './content-gallery';
|
|
import { ContentSkeleton } from './content-skeleton';
|
|
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 { balance } = useBalance();
|
|
const { width } = useWindowDimensions();
|
|
const [billingMode, setBillingMode] = useState<BillingMode>('monthly');
|
|
const [activeTab, setActiveTab] = useState<TabKey>('all');
|
|
const [isEditingIdentity, setIsEditingIdentity] = useState(false);
|
|
const [editedIdentity, setEditedIdentity] = useState<{
|
|
name?: string;
|
|
avatar?: ImageSourcePropType;
|
|
} | null>(null);
|
|
|
|
const {
|
|
generations,
|
|
isLoading,
|
|
error,
|
|
isRefreshing,
|
|
isLoadingMore,
|
|
hasMore,
|
|
isTabSwitching,
|
|
handleRefresh,
|
|
handleLoadMore,
|
|
} = useProfileData(activeTab);
|
|
|
|
const isSmallScreen = width < PROFILE_THEME.breakpoints.small;
|
|
const horizontalPadding = isSmallScreen
|
|
? PROFILE_THEME.spacing.horizontal.small
|
|
: PROFILE_THEME.spacing.horizontal.large;
|
|
const avatarSize = isSmallScreen
|
|
? PROFILE_THEME.avatar.size.small
|
|
: PROFILE_THEME.avatar.size.large;
|
|
|
|
const displayNameFromUser = deriveDisplayName(user) ?? 'prairie_pufferfish_the';
|
|
const avatarSource = deriveAvatarSource(user);
|
|
const creditBalance = balance.remainingTokenBalance;
|
|
const stats = createStats(user);
|
|
|
|
const presentedDisplayName = editedIdentity?.name ?? displayNameFromUser;
|
|
const presentedAvatar = editedIdentity?.avatar ?? avatarSource;
|
|
|
|
const headerContainerStyle = useMemo(
|
|
() => ({
|
|
paddingHorizontal: horizontalPadding,
|
|
paddingBottom: PROFILE_THEME.spacing.headerBottom,
|
|
}),
|
|
[horizontalPadding]
|
|
);
|
|
|
|
const headerComponent = useMemo(
|
|
() => (
|
|
<View style={headerContainerStyle}>
|
|
<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}
|
|
isLoading={isTabSwitching}
|
|
/>
|
|
|
|
<Divider />
|
|
</View>
|
|
),
|
|
[
|
|
headerContainerStyle,
|
|
billingMode,
|
|
creditBalance,
|
|
presentedDisplayName,
|
|
presentedAvatar,
|
|
avatarSize,
|
|
stats,
|
|
activeTab,
|
|
isTabSwitching,
|
|
]
|
|
);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<PageLayout backgroundColor={PROFILE_THEME.colors.background} horizontalPadding={false}>
|
|
<ProfileLoadingState />
|
|
</PageLayout>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<PageLayout backgroundColor={PROFILE_THEME.colors.background} horizontalPadding={false}>
|
|
<ProfileErrorState onRetry={handleRefresh} />
|
|
</PageLayout>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<PageLayout backgroundColor={PROFILE_THEME.colors.background} horizontalPadding={false}>
|
|
{isLoading && generations.length === 0 ? (
|
|
<View style={styles.flexContainer}>
|
|
{headerComponent}
|
|
<ContentSkeleton />
|
|
</View>
|
|
) : generations.length === 0 && !isTabSwitching ? (
|
|
<View style={styles.flexContainer}>
|
|
{headerComponent}
|
|
<ProfileEmptyState activeTab={activeTab} />
|
|
</View>
|
|
) : (
|
|
<ContentGallery
|
|
generations={generations}
|
|
isRefreshing={isRefreshing}
|
|
onRefresh={handleRefresh}
|
|
isLoadingMore={isLoadingMore}
|
|
hasMore={hasMore}
|
|
onLoadMore={handleLoadMore}
|
|
ListHeaderComponent={headerComponent}
|
|
/>
|
|
)}
|
|
|
|
<ProfileEditModal
|
|
visible={isEditingIdentity}
|
|
avatarSource={presentedAvatar}
|
|
initialName={presentedDisplayName}
|
|
onClose={() => setIsEditingIdentity(false)}
|
|
onSave={({ name, avatar }) => {
|
|
setEditedIdentity({ name, avatar });
|
|
setIsEditingIdentity(false);
|
|
}}
|
|
/>
|
|
</PageLayout>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
flexContainer: {
|
|
flex: 1,
|
|
},
|
|
});
|
|
|
|
export default ProfileScreen;
|