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
+211
View File
@@ -0,0 +1,211 @@
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { PreferencePill } from '@/components/common/PreferencePill';
import { ScreenContainer } from '@/components/common/ScreenContainer';
import { shootingStyleOptions } from '@/data/shootingStyles';
import { useAppStore } from '@/store/useAppStore';
import { colors } from '@/theme/colors';
import { spacing } from '@/theme/spacing';
import { TargetFacePreference } from '@/types';
function formatTargetFace(targetFace: TargetFacePreference): string {
return targetFace === 'single-face' ? 'Single Face' : 'Multi Face';
}
export function ProfileScreen(): React.JSX.Element {
const archerProfile = useAppStore((state) => state.archerProfile);
const toggleStyleEnabled = useAppStore((state) => state.toggleStyleEnabled);
const setStyleTargetFace = useAppStore((state) => state.setStyleTargetFace);
const setActiveStyle = useAppStore((state) => state.setActiveStyle);
const activePreference = archerProfile.stylePreferences.find(
(preference) => preference.styleId === archerProfile.activeStyleId
);
const activeStyle = shootingStyleOptions.find((style) => style.id === activePreference?.styleId);
return (
<ScreenContainer>
<Text style={styles.title}>Archer profile</Text>
<Text style={styles.subtitle}>
Set up the shooting styles you use locally and choose the default target-face layout for
each one. Later, practice and league sessions can read these preferences automatically.
</Text>
<View style={styles.summaryCard}>
<Text style={styles.summaryLabel}>Current default setup</Text>
<Text style={styles.summaryValue}>
{activePreference && activeStyle
? `${activeStyle.label}${formatTargetFace(activePreference.defaultTargetFace)}`
: 'No default shooting style selected yet'}
</Text>
</View>
<View style={styles.list}>
{shootingStyleOptions.map((style) => {
const preference = archerProfile.stylePreferences.find(
(entry) => entry.styleId === style.id
);
const isEnabled = Boolean(preference?.enabled);
const isActive = archerProfile.activeStyleId === style.id;
return (
<View key={style.id} style={[styles.styleCard, isActive && styles.styleCardActive]}>
<View style={styles.styleHeader}>
<View style={styles.styleText}>
<Text style={styles.styleTitle}>{style.label}</Text>
<Text style={styles.styleDescription}>{style.description}</Text>
</View>
<Pressable
onPress={() => toggleStyleEnabled(style.id)}
style={[styles.enableButton, isEnabled && styles.enableButtonEnabled]}
>
<Text style={[styles.enableButtonText, isEnabled && styles.enableButtonTextEnabled]}>
{isEnabled ? 'Enabled' : 'Enable'}
</Text>
</Pressable>
</View>
<View style={styles.inlineRow}>
<PreferencePill
label="Set Default"
selected={isActive}
onPress={() => setActiveStyle(style.id)}
/>
<Text style={styles.helperText}>
{isEnabled
? 'Ready to be used as the default style for new sessions.'
: 'Enable this style before making it your default.'}
</Text>
</View>
<View style={styles.targetFaceSection}>
<Text style={styles.targetFaceLabel}>Default target face</Text>
<View style={styles.pillRow}>
<PreferencePill
label="Single Face"
selected={preference?.defaultTargetFace === 'single-face'}
onPress={() => setStyleTargetFace(style.id, 'single-face')}
/>
<PreferencePill
label="Multi Face"
selected={preference?.defaultTargetFace === 'multi-face'}
onPress={() => setStyleTargetFace(style.id, 'multi-face')}
/>
</View>
</View>
</View>
);
})}
</View>
</ScreenContainer>
);
}
const styles = StyleSheet.create({
title: {
fontSize: 30,
fontWeight: '800',
color: colors.text,
},
subtitle: {
color: colors.muted,
lineHeight: 22,
},
summaryCard: {
borderRadius: 20,
padding: spacing.md,
backgroundColor: colors.surfaceAlt,
borderWidth: 1,
borderColor: colors.border,
gap: spacing.xs,
},
summaryLabel: {
color: colors.muted,
fontSize: 13,
textTransform: 'uppercase',
letterSpacing: 1,
fontWeight: '700',
},
summaryValue: {
color: colors.text,
fontSize: 18,
fontWeight: '700',
},
list: {
gap: spacing.md,
},
styleCard: {
borderRadius: 22,
padding: spacing.md,
backgroundColor: colors.surfaceAlt,
borderWidth: 1,
borderColor: colors.border,
gap: spacing.md,
},
styleCardActive: {
borderColor: colors.primary,
shadowColor: colors.primary,
shadowOpacity: 0.18,
shadowRadius: 14,
shadowOffset: { width: 0, height: 6 },
elevation: 3,
},
styleHeader: {
flexDirection: 'row',
gap: spacing.md,
alignItems: 'flex-start',
},
styleText: {
flex: 1,
gap: spacing.xs,
},
styleTitle: {
color: colors.text,
fontSize: 20,
fontWeight: '700',
},
styleDescription: {
color: colors.muted,
lineHeight: 20,
},
enableButton: {
borderRadius: 999,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderWidth: 1,
borderColor: colors.border,
backgroundColor: colors.surface,
},
enableButtonEnabled: {
backgroundColor: colors.primary,
borderColor: colors.primarySoft,
},
enableButtonText: {
color: colors.text,
fontWeight: '700',
},
enableButtonTextEnabled: {
color: '#04111F',
},
inlineRow: {
gap: spacing.sm,
},
helperText: {
color: colors.muted,
lineHeight: 20,
},
targetFaceSection: {
gap: spacing.sm,
},
targetFaceLabel: {
color: colors.text,
fontWeight: '700',
},
pillRow: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.sm,
},
});