- Replace all Ionicons with native SF Symbols via expo-symbols SymbolView - Create reusable Icon wrapper component (src/shared/components/Icon.tsx) - Remove @expo/vector-icons and lucide-react dependencies - Refactor explore tab with filters, search, and category browsing - Add collections and programs data with Supabase integration - Add explore filter store and filter sheet - Update i18n strings (en, de, es, fr) for new explore features - Update test mocks and remove stale snapshots - Add user fitness level to user store and types
213 lines
6.0 KiB
TypeScript
213 lines
6.0 KiB
TypeScript
/**
|
|
* TabataFit Privacy Policy Screen
|
|
* Required for App Store submission
|
|
*/
|
|
|
|
import React from 'react'
|
|
import { View, ScrollView, StyleSheet, Text, Pressable } from 'react-native'
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
import { useRouter } from 'expo-router'
|
|
import { Icon } from '@/src/shared/components/Icon'
|
|
|
|
import { useTranslation } from 'react-i18next'
|
|
import { useHaptics } from '@/src/shared/hooks'
|
|
import { darkColors, BRAND } from '@/src/shared/theme'
|
|
import { SPACING } from '@/src/shared/constants/spacing'
|
|
|
|
export default function PrivacyPolicyScreen() {
|
|
const { t } = useTranslation('screens')
|
|
const router = useRouter()
|
|
const insets = useSafeAreaInsets()
|
|
const haptics = useHaptics()
|
|
|
|
const handleClose = () => {
|
|
haptics.selection()
|
|
router.back()
|
|
}
|
|
|
|
return (
|
|
<View style={[styles.container, { paddingTop: insets.top }]}>
|
|
{/* Header */}
|
|
<View style={styles.header}>
|
|
<Pressable style={styles.backButton} onPress={handleClose}>
|
|
<Icon name="chevron.left" size={28} color={darkColors.text.primary} />
|
|
</Pressable>
|
|
<Text style={styles.headerTitle}>{t('privacy.title')}</Text>
|
|
<View style={{ width: 44 }} />
|
|
</View>
|
|
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
contentContainerStyle={[
|
|
styles.content,
|
|
{ paddingBottom: insets.bottom + 40 },
|
|
]}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
<Section title={t('privacy.lastUpdated')} />
|
|
|
|
<Section title={t('privacy.intro.title')}>
|
|
<Paragraph>{t('privacy.intro.content')}</Paragraph>
|
|
</Section>
|
|
|
|
<Section title={t('privacy.dataCollection.title')}>
|
|
<Paragraph>{t('privacy.dataCollection.content')}</Paragraph>
|
|
<BulletList
|
|
items={[
|
|
t('privacy.dataCollection.items.workouts'),
|
|
t('privacy.dataCollection.items.settings'),
|
|
t('privacy.dataCollection.items.device'),
|
|
]}
|
|
/>
|
|
</Section>
|
|
|
|
<Section title={t('privacy.usage.title')}>
|
|
<Paragraph>{t('privacy.usage.content')}</Paragraph>
|
|
</Section>
|
|
|
|
<Section title={t('privacy.sharing.title')}>
|
|
<Paragraph>{t('privacy.sharing.content')}</Paragraph>
|
|
</Section>
|
|
|
|
<Section title={t('privacy.security.title')}>
|
|
<Paragraph>{t('privacy.security.content')}</Paragraph>
|
|
</Section>
|
|
|
|
<Section title={t('privacy.rights.title')}>
|
|
<Paragraph>{t('privacy.rights.content')}</Paragraph>
|
|
</Section>
|
|
|
|
<Section title={t('privacy.contact.title')}>
|
|
<Paragraph>{t('privacy.contact.content')}</Paragraph>
|
|
<Text style={styles.email}>privacy@tabatafit.app</Text>
|
|
</Section>
|
|
|
|
<View style={styles.footer}>
|
|
<Text style={styles.footerText}>
|
|
TabataFit v1.0.0
|
|
</Text>
|
|
</View>
|
|
</ScrollView>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// HELPER COMPONENTS
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
function Section({
|
|
title,
|
|
children,
|
|
}: {
|
|
title: string
|
|
children?: React.ReactNode
|
|
}) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{title}</Text>
|
|
{children}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
function Paragraph({ children }: { children: string }) {
|
|
return <Text style={styles.paragraph}>{children}</Text>
|
|
}
|
|
|
|
function BulletList({ items }: { items: string[] }) {
|
|
return (
|
|
<View style={styles.bulletList}>
|
|
{items.map((item, index) => (
|
|
<View key={index} style={styles.bulletItem}>
|
|
<Text style={styles.bullet}>•</Text>
|
|
<Text style={styles.bulletText}>{item}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// STYLES
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: darkColors.bg.base,
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: SPACING[4],
|
|
paddingVertical: SPACING[3],
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: darkColors.border.glass,
|
|
},
|
|
backButton: {
|
|
width: 44,
|
|
height: 44,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
headerTitle: {
|
|
fontSize: 17,
|
|
fontWeight: '600',
|
|
color: darkColors.text.primary,
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
},
|
|
content: {
|
|
paddingHorizontal: SPACING[5],
|
|
paddingTop: SPACING[4],
|
|
},
|
|
section: {
|
|
marginBottom: SPACING[6],
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 20,
|
|
fontWeight: '700',
|
|
color: darkColors.text.primary,
|
|
marginBottom: SPACING[3],
|
|
},
|
|
paragraph: {
|
|
fontSize: 15,
|
|
lineHeight: 22,
|
|
color: darkColors.text.secondary,
|
|
},
|
|
bulletList: {
|
|
marginTop: SPACING[3],
|
|
},
|
|
bulletItem: {
|
|
flexDirection: 'row',
|
|
marginBottom: SPACING[2],
|
|
},
|
|
bullet: {
|
|
fontSize: 15,
|
|
color: BRAND.PRIMARY,
|
|
marginRight: SPACING[2],
|
|
},
|
|
bulletText: {
|
|
flex: 1,
|
|
fontSize: 15,
|
|
lineHeight: 22,
|
|
color: darkColors.text.secondary,
|
|
},
|
|
email: {
|
|
fontSize: 15,
|
|
color: BRAND.PRIMARY,
|
|
marginTop: SPACING[2],
|
|
},
|
|
footer: {
|
|
marginTop: SPACING[8],
|
|
alignItems: 'center',
|
|
},
|
|
footerText: {
|
|
fontSize: 13,
|
|
color: darkColors.text.tertiary,
|
|
},
|
|
})
|