Files
tabatago/app/privacy.tsx
Millian Lamiaux 2ad7ae3a34 feat: Apple Watch app + Paywall + Privacy Policy + rebranding
## Major Features
- Apple Watch companion app (6 phases complete)
  - WatchConnectivity iPhone ↔ Watch
  - HealthKit integration (HR, calories)
  - SwiftUI premium UI
  - 9 complication types
  - Always-On Display support

- Paywall screen with RevenueCat integration
- Privacy Policy screen
- App rebranding: tabatago → TabataFit
- Bundle ID: com.millianlmx.tabatafit

## Changes
- New: ios/TabataFit Watch App/ (complete Watch app)
- New: app/paywall.tsx (subscription UI)
- New: app/privacy.tsx (privacy policy)
- New: src/features/watch/ (Watch sync hooks)
- New: admin-web/ (admin dashboard)
- Updated: app.json, package.json (branding)
- Updated: profile.tsx (paywall + privacy links)
- Updated: i18n translations (EN/FR/DE/ES)
- New: app icon 1024x1024

## Watch App Files
- TabataFitWatchApp.swift (entry point)
- ContentView.swift (premium UI)
- HealthKitManager.swift (HR + calories)
- WatchSessionManager.swift (communication)
- Complications/ (WidgetKit)
- UserDefaults+Shared.swift (data sharing)
2026-03-11 09:43:53 +01:00

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 Ionicons from '@expo/vector-icons/Ionicons'
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}>
<Ionicons name="chevron-back" 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,
},
})