155 lines
5.3 KiB
TypeScript
155 lines
5.3 KiB
TypeScript
import React from 'react';
|
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
|
|
import { SetupSection } from '@/components/common/SetupSection';
|
|
import { ScreenContainer } from '@/components/common/ScreenContainer';
|
|
import { StatCard } from '@/components/common/StatCard';
|
|
import { shootingStyleOptions } from '@/data/shootingStyles';
|
|
import { ModesStackParamList } from '@/navigation/types';
|
|
import { useAppStore } from '@/store/useAppStore';
|
|
import { colors } from '@/theme/colors';
|
|
import { spacing } from '@/theme/spacing';
|
|
|
|
type Props = NativeStackScreenProps<ModesStackParamList, 'PracticeMode'>;
|
|
|
|
export function PracticeModeScreen({ navigation }: Props): React.JSX.Element {
|
|
const archerProfile = useAppStore((state) => state.archerProfile);
|
|
const sessionSetup = useAppStore((state) => state.sessionSetups.practice);
|
|
const setSessionStyle = useAppStore((state) => state.setSessionStyle);
|
|
const setSessionTargetFace = useAppStore((state) => state.setSessionTargetFace);
|
|
const applyProfileDefaultsToSessions = useAppStore(
|
|
(state) => state.applyProfileDefaultsToSessions
|
|
);
|
|
const activeSession = useAppStore((state) => state.activeSession);
|
|
const startSession = useAppStore((state) => state.startSession);
|
|
const clearActiveSession = useAppStore((state) => state.clearActiveSession);
|
|
|
|
const enabledStyles = shootingStyleOptions.filter((style) =>
|
|
archerProfile.stylePreferences.some(
|
|
(preference) => preference.styleId === style.id && preference.enabled
|
|
)
|
|
);
|
|
const activeStyleLabel =
|
|
shootingStyleOptions.find((style) => style.id === sessionSetup.styleId)?.label ?? 'Not set';
|
|
const livePracticeStyle =
|
|
shootingStyleOptions.find((style) => style.id === activeSession?.styleId)?.label ?? 'Not set';
|
|
const practiceIsActive = activeSession?.mode === 'practice';
|
|
|
|
return (
|
|
<ScreenContainer>
|
|
<Text style={styles.title}>Practice mode</Text>
|
|
<Text style={styles.subtitle}>
|
|
Start by confirming the profile defaults for this practice session, then layer in drills,
|
|
timers, notes, and equipment tuning checkpoints.
|
|
</Text>
|
|
<SetupSection
|
|
mode="practice"
|
|
title="Practice setup"
|
|
description="Use your local profile as a starting point, then override style or target face for this session if needed."
|
|
selectedStyleId={sessionSetup.styleId}
|
|
selectedTargetFace={sessionSetup.targetFace}
|
|
enabledStyles={enabledStyles}
|
|
canStart={Boolean(sessionSetup.styleId)}
|
|
startLabel={practiceIsActive ? 'Resume Practice' : 'Start Practice'}
|
|
onStyleSelect={(styleId) => setSessionStyle('practice', styleId)}
|
|
onTargetFaceSelect={(targetFace) => setSessionTargetFace('practice', targetFace)}
|
|
onApplyDefaults={applyProfileDefaultsToSessions}
|
|
onStart={() => {
|
|
if (!practiceIsActive) {
|
|
startSession('practice');
|
|
}
|
|
navigation.navigate('PracticeSession');
|
|
}}
|
|
/>
|
|
{practiceIsActive ? (
|
|
<View style={styles.activeSessionCard}>
|
|
<View style={styles.activeSessionHeader}>
|
|
<View style={styles.activeSessionText}>
|
|
<Text style={styles.activeSessionLabel}>Practice session live</Text>
|
|
<Text style={styles.activeSessionValue}>
|
|
{livePracticeStyle} •{' '}
|
|
{activeSession.targetFace === 'single-face' ? 'Single Face' : 'Multi Face'}
|
|
</Text>
|
|
</View>
|
|
<Pressable onPress={clearActiveSession} style={styles.endButton}>
|
|
<Text style={styles.endButtonText}>End Session</Text>
|
|
</Pressable>
|
|
</View>
|
|
<Text style={styles.activeSessionMeta}>
|
|
Started at {new Date(activeSession.startedAt).toLocaleTimeString()}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
<View style={styles.statsRow}>
|
|
<StatCard label="Practice style" value={activeStyleLabel} />
|
|
<StatCard
|
|
label="Default face"
|
|
value={sessionSetup.targetFace === 'single-face' ? 'Single Face' : 'Multi Face'}
|
|
/>
|
|
<StatCard label="Current drill" value="Blank Bale" />
|
|
</View>
|
|
</ScreenContainer>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
title: {
|
|
fontSize: 30,
|
|
fontWeight: '800',
|
|
color: colors.text,
|
|
},
|
|
subtitle: {
|
|
color: colors.muted,
|
|
lineHeight: 22,
|
|
},
|
|
statsRow: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: spacing.md,
|
|
},
|
|
activeSessionCard: {
|
|
borderRadius: 20,
|
|
padding: spacing.md,
|
|
backgroundColor: colors.surfaceAlt,
|
|
borderWidth: 1,
|
|
borderColor: colors.success,
|
|
gap: spacing.sm,
|
|
},
|
|
activeSessionHeader: {
|
|
flexDirection: 'row',
|
|
gap: spacing.md,
|
|
alignItems: 'flex-start',
|
|
},
|
|
activeSessionText: {
|
|
flex: 1,
|
|
gap: spacing.xs,
|
|
},
|
|
activeSessionLabel: {
|
|
color: colors.success,
|
|
fontSize: 13,
|
|
fontWeight: '800',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 1,
|
|
},
|
|
activeSessionValue: {
|
|
color: colors.text,
|
|
fontSize: 20,
|
|
fontWeight: '800',
|
|
},
|
|
activeSessionMeta: {
|
|
color: colors.muted,
|
|
},
|
|
endButton: {
|
|
borderRadius: 999,
|
|
paddingHorizontal: spacing.md,
|
|
paddingVertical: spacing.sm,
|
|
borderWidth: 1,
|
|
borderColor: colors.border,
|
|
},
|
|
endButtonText: {
|
|
color: colors.text,
|
|
fontWeight: '700',
|
|
},
|
|
});
|