app framework

This commit is contained in:
blaisadmin
2026-03-27 00:38:12 -04:00
parent a395f9422c
commit bba670491e
39 changed files with 11781 additions and 1 deletions
+154
View File
@@ -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',
},
});