refactor: code quality cleanup — remove any types, add logger, rename Kine to Tabata
- Phase 0: Rename all Kine references to Tabata (types, files, imports, i18n, analytics events) - Phase 1: Add test coverage for tabataProgramStore, workoutProgramStore, and color utils (47 tests) - Phase 2: Remove all `any` types from production code with proper typed replacements - Phase 3: Replace ~60 raw console.* calls with __DEV__-gated logger utility - Phase 4: Verify .DS_Store housekeeping (already clean) 0 TypeScript errors, 583/583 tests passing.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* TabataFit Workout Complete Screen
|
||||
* Celebration with real data from activity store
|
||||
* Dark Medical design system — navy, green, no glass
|
||||
*/
|
||||
|
||||
import { useRef, useEffect, useMemo, useState } from 'react'
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
} from 'react-native'
|
||||
import { useRouter, useLocalSearchParams } from 'expo-router'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { BlurView } from 'expo-blur'
|
||||
import { Icon, type IconName } from '@/src/shared/components/Icon'
|
||||
|
||||
import * as Sharing from 'expo-sharing'
|
||||
@@ -26,15 +26,17 @@ import { useActivityStore, useUserStore } from '@/src/shared/stores'
|
||||
import { getWorkoutById, getPopularWorkouts, getTrainerById, getWorkoutAccentColor } from '@/src/shared/data'
|
||||
import { useTranslatedWorkout, useTranslatedWorkouts } from '@/src/shared/data/useTranslatedData'
|
||||
import { SyncConsentModal } from '@/src/shared/components/SyncConsentModal'
|
||||
import { NativeButton } from '@/src/shared/components/native'
|
||||
import { enableSync } from '@/src/shared/services/sync'
|
||||
import type { WorkoutSessionData } from '@/src/shared/types'
|
||||
|
||||
import { useThemeColors, BRAND } from '@/src/shared/theme'
|
||||
import { useThemeColors } from '@/src/shared/theme'
|
||||
import type { ThemeColors } from '@/src/shared/theme/types'
|
||||
import { TYPOGRAPHY } from '@/src/shared/constants/typography'
|
||||
import { TYPOGRAPHY, FONT_FAMILY } from '@/src/shared/constants/typography'
|
||||
import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
|
||||
import { RADIUS } from '@/src/shared/constants/borderRadius'
|
||||
import { SPRING, EASE } from '@/src/shared/constants/animations'
|
||||
import { GREEN, NAVY, TEXT, BORDER_COLORS } from '@/src/shared/constants/colors'
|
||||
|
||||
const { width: SCREEN_WIDTH } = Dimensions.get('window')
|
||||
|
||||
@@ -79,7 +81,7 @@ function SecondaryButton({
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Animated.View style={[styles.secondaryButton, { transform: [{ scale: scaleAnim }] }]}>
|
||||
{icon && <Icon name={icon} size={18} tintColor={colors.text.primary} style={styles.buttonIcon} />}
|
||||
{icon && <Icon name={icon} size={18} tintColor={TEXT.PRIMARY} style={styles.buttonIcon} />}
|
||||
<RNText style={styles.secondaryButtonText}>{children}</RNText>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
@@ -94,7 +96,6 @@ function PrimaryButton({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const colors = useThemeColors()
|
||||
const isDark = colors.colorScheme === 'dark'
|
||||
const styles = useMemo(() => createStyles(colors), [colors])
|
||||
const scaleAnim = useRef(new Animated.Value(1)).current
|
||||
|
||||
@@ -124,10 +125,10 @@ function PrimaryButton({
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.primaryButton,
|
||||
{ backgroundColor: isDark ? '#FFFFFF' : '#000000', transform: [{ scale: scaleAnim }] },
|
||||
{ backgroundColor: GREEN['500'], transform: [{ scale: scaleAnim }] },
|
||||
]}
|
||||
>
|
||||
<RNText style={[styles.primaryButtonText, { color: isDark ? '#000000' : '#FFFFFF' }]}>
|
||||
<RNText style={[styles.primaryButtonText, { color: NAVY['900'] }]}>
|
||||
{children}
|
||||
</RNText>
|
||||
</Animated.View>
|
||||
@@ -217,8 +218,7 @@ function StatCard({
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.statCard, { transform: [{ scale: scaleAnim }] }]}>
|
||||
<BlurView intensity={colors.glass.blurMedium} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
|
||||
<Icon name={icon} size={24} tintColor={accentColor} />
|
||||
<Icon name={icon} size={24} tintColor={GREEN['500']} />
|
||||
<RNText style={styles.statValue}>{value}</RNText>
|
||||
<RNText style={styles.statLabel}>{label}</RNText>
|
||||
</Animated.View>
|
||||
@@ -248,9 +248,9 @@ function BurnBarResult({ percentile, accentColor }: { percentile: number; accent
|
||||
return (
|
||||
<View style={styles.burnBarContainer}>
|
||||
<RNText style={styles.burnBarTitle}>{t('screens:complete.burnBar')}</RNText>
|
||||
<RNText style={[styles.burnBarResult, { color: accentColor }]}>{t('screens:complete.burnBarResult', { percentile })}</RNText>
|
||||
<RNText style={[styles.burnBarResult, { color: GREEN['500'] }]}>{t('screens:complete.burnBarResult', { percentile })}</RNText>
|
||||
<View style={styles.burnBarTrack}>
|
||||
<Animated.View style={[styles.burnBarFill, { width: barWidth, backgroundColor: accentColor }]} />
|
||||
<Animated.View style={[styles.burnBarFill, { width: barWidth, backgroundColor: GREEN['500'] }]} />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
@@ -383,20 +383,20 @@ export default function WorkoutCompleteScreen() {
|
||||
|
||||
{/* Stats Grid */}
|
||||
<View style={styles.statsGrid}>
|
||||
<StatCard value={resultCalories} label={t('screens:complete.caloriesLabel')} icon="flame.fill" accentColor={trainerColor} delay={100} />
|
||||
<StatCard value={resultMinutes} label={t('screens:complete.minutesLabel')} icon="clock.fill" accentColor={trainerColor} delay={200} />
|
||||
<StatCard value="100%" label={t('screens:complete.completeLabel')} icon="checkmark.circle.fill" accentColor={trainerColor} delay={300} />
|
||||
<StatCard value={resultCalories} label={t('screens:complete.caloriesLabel')} icon="flame.fill" accentColor={GREEN['500']} delay={100} />
|
||||
<StatCard value={resultMinutes} label={t('screens:complete.minutesLabel')} icon="clock.fill" accentColor={GREEN['500']} delay={200} />
|
||||
<StatCard value="100%" label={t('screens:complete.completeLabel')} icon="checkmark.circle.fill" accentColor={GREEN['500']} delay={300} />
|
||||
</View>
|
||||
|
||||
{/* Burn Bar */}
|
||||
<BurnBarResult percentile={burnBarPercentile} accentColor={trainerColor} />
|
||||
<BurnBarResult percentile={burnBarPercentile} accentColor={GREEN['500']} />
|
||||
|
||||
<View style={styles.divider} />
|
||||
|
||||
{/* Streak */}
|
||||
<View style={styles.streakSection}>
|
||||
<View style={[styles.streakBadge, { backgroundColor: trainerColor + '26' }]}>
|
||||
<Icon name="flame.fill" size={32} tintColor={trainerColor} />
|
||||
<View style={[styles.streakBadge, { backgroundColor: GREEN.DIM }]}>
|
||||
<Icon name="flame.fill" size={32} tintColor={GREEN['500']} />
|
||||
</View>
|
||||
<View style={styles.streakInfo}>
|
||||
<RNText style={styles.streakTitle}>{t('screens:complete.streakTitle', { count: streak.current })}</RNText>
|
||||
@@ -408,9 +408,13 @@ export default function WorkoutCompleteScreen() {
|
||||
|
||||
{/* Share Button */}
|
||||
<View style={styles.shareSection}>
|
||||
<SecondaryButton onPress={handleShare} icon="square.and.arrow.up">
|
||||
{t('screens:complete.shareWorkout')}
|
||||
</SecondaryButton>
|
||||
<NativeButton
|
||||
variant="ghost"
|
||||
title={t('screens:complete.shareWorkout')}
|
||||
systemImage="square.and.arrow.up"
|
||||
onPress={handleShare}
|
||||
fullWidth
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.divider} />
|
||||
@@ -425,9 +429,8 @@ export default function WorkoutCompleteScreen() {
|
||||
onPress={() => handleWorkoutPress(w.id)}
|
||||
style={styles.recommendedCard}
|
||||
>
|
||||
<BlurView intensity={colors.glass.blurLight} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
|
||||
<View style={[styles.recommendedThumb, { backgroundColor: trainerColor + '20' }]}>
|
||||
<Icon name="flame.fill" size={24} tintColor={trainerColor} />
|
||||
<View style={[styles.recommendedThumb, { backgroundColor: GREEN.DIM }]}>
|
||||
<Icon name="flame.fill" size={24} tintColor={GREEN['500']} />
|
||||
</View>
|
||||
<RNText style={styles.recommendedTitleText} numberOfLines={1}>{w.title}</RNText>
|
||||
<RNText style={styles.recommendedDurationText}>{t('units.minUnit', { count: w.duration })}</RNText>
|
||||
@@ -439,11 +442,14 @@ export default function WorkoutCompleteScreen() {
|
||||
|
||||
{/* Fixed Bottom Button */}
|
||||
<View style={[styles.bottomBar, { paddingBottom: insets.bottom + SPACING[4] }]}>
|
||||
<BlurView intensity={colors.glass.blurHeavy} tint={colors.glass.blurTint} style={StyleSheet.absoluteFill} />
|
||||
<View style={styles.homeButtonContainer}>
|
||||
<PrimaryButton onPress={handleGoHome}>
|
||||
{t('screens:complete.backToHome')}
|
||||
</PrimaryButton>
|
||||
<NativeButton
|
||||
variant="primary"
|
||||
title={t('screens:complete.backToHome')}
|
||||
onPress={handleGoHome}
|
||||
fullWidth
|
||||
controlSize="large"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -481,27 +487,27 @@ function createStyles(colors: ThemeColors) {
|
||||
justifyContent: 'center',
|
||||
paddingVertical: SPACING[3],
|
||||
paddingHorizontal: SPACING[4],
|
||||
borderRadius: RADIUS.LG,
|
||||
borderRadius: RADIUS.MD,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glassStrong,
|
||||
borderColor: BORDER_COLORS.DIM,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
secondaryButtonText: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
color: colors.text.primary,
|
||||
fontWeight: '600',
|
||||
color: TEXT.PRIMARY,
|
||||
fontFamily: FONT_FAMILY.SANS_SEMIBOLD,
|
||||
},
|
||||
primaryButton: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: SPACING[4],
|
||||
paddingHorizontal: SPACING[6],
|
||||
borderRadius: RADIUS.LG,
|
||||
borderRadius: RADIUS.MD,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
primaryButtonText: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
fontWeight: '700',
|
||||
fontFamily: FONT_FAMILY.SANS_BOLD,
|
||||
},
|
||||
buttonIcon: {
|
||||
marginRight: SPACING[2],
|
||||
@@ -518,7 +524,7 @@ function createStyles(colors: ThemeColors) {
|
||||
},
|
||||
celebrationTitle: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
letterSpacing: 2,
|
||||
},
|
||||
ringsContainer: {
|
||||
@@ -530,23 +536,21 @@ function createStyles(colors: ThemeColors) {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
backgroundColor: colors.border.glass,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: colors.border.glassStrong,
|
||||
},
|
||||
ring1: {
|
||||
borderColor: BRAND.PRIMARY,
|
||||
backgroundColor: 'rgba(255, 107, 53, 0.15)',
|
||||
borderColor: GREEN['500'],
|
||||
backgroundColor: GREEN.DIM,
|
||||
},
|
||||
ring2: {
|
||||
borderColor: '#30D158',
|
||||
backgroundColor: 'rgba(48, 209, 88, 0.15)',
|
||||
borderColor: GREEN['500'],
|
||||
backgroundColor: GREEN.DIM,
|
||||
},
|
||||
ring3: {
|
||||
borderColor: '#5AC8FA',
|
||||
backgroundColor: 'rgba(90, 200, 250, 0.15)',
|
||||
borderColor: GREEN['500'],
|
||||
backgroundColor: GREEN.DIM,
|
||||
},
|
||||
ringEmoji: {
|
||||
fontSize: 28,
|
||||
@@ -562,19 +566,20 @@ function createStyles(colors: ThemeColors) {
|
||||
width: (SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2 - SPACING[4]) / 3,
|
||||
padding: SPACING[3],
|
||||
borderRadius: RADIUS.LG,
|
||||
backgroundColor: colors.surface.default.backgroundColor,
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glass,
|
||||
borderColor: colors.surface.default.borderColor,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
statValue: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
marginTop: SPACING[2],
|
||||
},
|
||||
statLabel: {
|
||||
...TYPOGRAPHY.CAPTION_2,
|
||||
color: colors.text.tertiary,
|
||||
color: TEXT.TERTIARY,
|
||||
marginTop: SPACING[1],
|
||||
},
|
||||
|
||||
@@ -584,7 +589,7 @@ function createStyles(colors: ThemeColors) {
|
||||
},
|
||||
burnBarTitle: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: colors.text.tertiary,
|
||||
color: TEXT.TERTIARY,
|
||||
},
|
||||
burnBarResult: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
@@ -594,18 +599,18 @@ function createStyles(colors: ThemeColors) {
|
||||
burnBarTrack: {
|
||||
height: 8,
|
||||
backgroundColor: colors.bg.surface,
|
||||
borderRadius: 4,
|
||||
borderRadius: RADIUS.SM,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
burnBarFill: {
|
||||
height: '100%',
|
||||
borderRadius: 4,
|
||||
borderRadius: RADIUS.SM,
|
||||
},
|
||||
|
||||
// Divider
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: colors.border.glass,
|
||||
backgroundColor: BORDER_COLORS.DIM,
|
||||
marginVertical: SPACING[2],
|
||||
},
|
||||
|
||||
@@ -619,7 +624,7 @@ function createStyles(colors: ThemeColors) {
|
||||
streakBadge: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
borderRadius: RADIUS.FULL,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@@ -628,11 +633,11 @@ function createStyles(colors: ThemeColors) {
|
||||
},
|
||||
streakTitle: {
|
||||
...TYPOGRAPHY.TITLE_2,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
streakSubtitle: {
|
||||
...TYPOGRAPHY.BODY,
|
||||
color: colors.text.tertiary,
|
||||
color: TEXT.TERTIARY,
|
||||
marginTop: SPACING[1],
|
||||
},
|
||||
|
||||
@@ -648,7 +653,7 @@ function createStyles(colors: ThemeColors) {
|
||||
},
|
||||
recommendedTitle: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
marginBottom: SPACING[4],
|
||||
},
|
||||
recommendedGrid: {
|
||||
@@ -660,7 +665,8 @@ function createStyles(colors: ThemeColors) {
|
||||
padding: SPACING[3],
|
||||
borderRadius: RADIUS.LG,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glass,
|
||||
borderColor: colors.surface.default.borderColor,
|
||||
backgroundColor: colors.surface.default.backgroundColor,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
recommendedThumb: {
|
||||
@@ -674,15 +680,15 @@ function createStyles(colors: ThemeColors) {
|
||||
},
|
||||
recommendedInitial: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
recommendedTitleText: {
|
||||
...TYPOGRAPHY.CARD_TITLE,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
recommendedDurationText: {
|
||||
...TYPOGRAPHY.CARD_METADATA,
|
||||
color: colors.text.tertiary,
|
||||
color: TEXT.TERTIARY,
|
||||
},
|
||||
|
||||
// Bottom Bar
|
||||
@@ -693,8 +699,9 @@ function createStyles(colors: ThemeColors) {
|
||||
right: 0,
|
||||
paddingHorizontal: LAYOUT.SCREEN_PADDING,
|
||||
paddingTop: SPACING[4],
|
||||
backgroundColor: colors.bg.base,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.border.glass,
|
||||
borderTopColor: BORDER_COLORS.DIM,
|
||||
},
|
||||
homeButtonContainer: {
|
||||
height: 56,
|
||||
|
||||
Reference in New Issue
Block a user