/** * TabataFit Profile Screen * React Native + SwiftUI Islands — wired to shared data */ import { View, StyleSheet, ScrollView, Text as RNText } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { LinearGradient } from 'expo-linear-gradient' import { BlurView } from 'expo-blur' import Ionicons from '@expo/vector-icons/Ionicons' import { Host, List, Section, Switch, Text, LabeledContent, DateTimePicker, Button, } from '@expo/ui/swift-ui' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useUserStore } from '@/src/shared/stores' import { requestNotificationPermissions, usePurchases } from '@/src/shared/hooks' import { StyledText } from '@/src/shared/components/StyledText' import { useThemeColors, BRAND, GRADIENTS } from '@/src/shared/theme' import type { ThemeColors } from '@/src/shared/theme/types' import { SPACING, LAYOUT } from '@/src/shared/constants/spacing' import { RADIUS } from '@/src/shared/constants/borderRadius' const FONTS = { LARGE_TITLE: 34, TITLE_2: 22, HEADLINE: 17, SUBHEADLINE: 15, CAPTION_1: 12, } // ═══════════════════════════════════════════════════════════════════════════ // MAIN SCREEN // ═══════════════════════════════════════════════════════════════════════════ export default function ProfileScreen() { const { t } = useTranslation('screens') const insets = useSafeAreaInsets() const colors = useThemeColors() const styles = useMemo(() => createStyles(colors), [colors]) const profile = useUserStore((s) => s.profile) const settings = useUserStore((s) => s.settings) const updateSettings = useUserStore((s) => s.updateSettings) const { restorePurchases } = usePurchases() const isPremium = profile.subscription !== 'free' const planLabel = isPremium ? 'TabataFit+' : 'Free' const handleRestore = async () => { await restorePurchases() } const handleReminderToggle = async (enabled: boolean) => { if (enabled) { const granted = await requestNotificationPermissions() if (!granted) return } updateSettings({ reminders: enabled }) } const handleTimeChange = (date: Date) => { const hh = String(date.getHours()).padStart(2, '0') const mm = String(date.getMinutes()).padStart(2, '0') updateSettings({ reminderTime: `${hh}:${mm}` }) } // Build initial date string for the picker (today at reminderTime) const today = new Date() const [rh, rm] = settings.reminderTime.split(':').map(Number) const pickerDate = new Date(today.getFullYear(), today.getMonth(), today.getDate(), rh, rm) const pickerInitial = pickerDate.toISOString() const settingsHeight = settings.reminders ? 430 : 385 return ( {/* Header */} {t('profile.title')} {/* Profile Card */} {profile.name[0]} {profile.name} {profile.email} {isPremium && ( {planLabel} )} {/* Subscription */} {isPremium && ( {planLabel} {t('profile.memberSince', { date: profile.joinDate })} )} {/* SwiftUI Island: Subscription Section */}
{/* SwiftUI Island: Settings */}
updateSettings({ haptics: v })} color={BRAND.PRIMARY} /> updateSettings({ soundEffects: v })} color={BRAND.PRIMARY} /> updateSettings({ voiceCoaching: v })} color={BRAND.PRIMARY} />
{settings.reminders && ( )}
{/* Version */} {t('profile.version')}
) } // ═══════════════════════════════════════════════════════════════════════════ // STYLES // ═══════════════════════════════════════════════════════════════════════════ function createStyles(colors: ThemeColors) { return StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bg.base, }, scrollView: { flex: 1, }, scrollContent: { paddingHorizontal: LAYOUT.SCREEN_PADDING, }, // Profile Card profileCard: { borderRadius: RADIUS.GLASS_CARD, overflow: 'hidden', marginBottom: SPACING[6], marginTop: SPACING[4], alignItems: 'center', paddingVertical: SPACING[6], borderWidth: 1, borderColor: colors.border.glass, ...colors.shadow.md, }, avatarContainer: { width: 80, height: 80, borderRadius: 40, alignItems: 'center', justifyContent: 'center', marginBottom: SPACING[3], overflow: 'hidden', }, premiumBadge: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(255, 107, 53, 0.15)', paddingHorizontal: SPACING[3], paddingVertical: SPACING[1], borderRadius: RADIUS.FULL, marginTop: SPACING[3], gap: SPACING[1], }, // Subscription Card subscriptionCard: { height: 80, borderRadius: RADIUS.LG, overflow: 'hidden', marginBottom: SPACING[6], ...colors.shadow.BRAND_GLOW, }, subscriptionContent: { flex: 1, flexDirection: 'row', alignItems: 'center', paddingHorizontal: SPACING[4], gap: SPACING[3], }, subscriptionInfo: { flex: 1, }, // Version Text versionText: { textAlign: 'center', marginTop: SPACING[6], }, }) }