app framework
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user