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