This commit is contained in:
Timo Knuth 2026-01-12 00:45:19 +01:00
parent aa9bd3f0b6
commit 79c5f0075a
32 changed files with 856 additions and 824 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 366 KiB

BIN
assets/images/app_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 KiB

0
nul Normal file
View File

View File

@ -21,6 +21,7 @@ interface ButtonProps {
textStyle?: TextStyle; textStyle?: TextStyle;
accessibilityLabel?: string; accessibilityLabel?: string;
accessibilityHint?: string; accessibilityHint?: string;
icon?: React.ReactNode; // NEW
} }
export const Button: React.FC<ButtonProps> = ({ export const Button: React.FC<ButtonProps> = ({
@ -34,6 +35,7 @@ export const Button: React.FC<ButtonProps> = ({
textStyle, textStyle,
accessibilityLabel, accessibilityLabel,
accessibilityHint, accessibilityHint,
icon,
}) => { }) => {
const isDisabled = disabled || loading; const isDisabled = disabled || loading;
@ -70,7 +72,10 @@ export const Button: React.FC<ButtonProps> = ({
size="small" size="small"
/> />
) : ( ) : (
<Text style={textStyles}>{title}</Text> <>
{icon && icon}
<Text style={[textStyles, icon ? { marginLeft: 8 } : null]}>{title}</Text>
</>
)} )}
</TouchableOpacity> </TouchableOpacity>
); );

View File

@ -4,7 +4,7 @@ import { Button } from './Button';
import { spacing } from '../lib/theme'; import { spacing } from '../lib/theme';
interface ButtonGridProps { interface ButtonGridProps {
options: { value: string; label: string }[]; options: { value: string; label: string; icon?: React.ReactNode }[];
selectedValue: string; selectedValue: string;
onSelect: (value: string) => void; onSelect: (value: string) => void;
columns?: number; columns?: number;
@ -22,6 +22,7 @@ export const ButtonGrid: React.FC<ButtonGridProps> = ({
<View key={option.value} style={[styles.buttonWrapper, { width: `${100 / columns}%` }]}> <View key={option.value} style={[styles.buttonWrapper, { width: `${100 / columns}%` }]}>
<Button <Button
title={option.label} title={option.label}
icon={option.icon}
onPress={() => onSelect(option.value)} onPress={() => onSelect(option.value)}
variant={selectedValue === option.value ? 'primary' : 'outline'} variant={selectedValue === option.value ? 'primary' : 'outline'}
size="sm" size="sm"

View File

@ -8,7 +8,8 @@ export interface EmptyStateProps {
title: string; title: string;
description?: string; description?: string;
actionLabel?: string; actionLabel?: string;
onAction?: () => void; onAction?: () => void; // Restored
buttonStyle?: import('react-native').ViewStyle;
} }
export const EmptyState: React.FC<EmptyStateProps> = ({ export const EmptyState: React.FC<EmptyStateProps> = ({
@ -17,6 +18,7 @@ export const EmptyState: React.FC<EmptyStateProps> = ({
description, description,
actionLabel, actionLabel,
onAction, onAction,
buttonStyle,
}) => { }) => {
return ( return (
<View style={styles.container}> <View style={styles.container}>
@ -28,7 +30,8 @@ export const EmptyState: React.FC<EmptyStateProps> = ({
title={actionLabel} title={actionLabel}
onPress={onAction} onPress={onAction}
size="md" size="md"
style={styles.button} // Fix: Flatten the array to satisfy the strict ViewStyle type expected by Button
style={StyleSheet.flatten([styles.button, buttonStyle])}
/> />
)} )}
</View> </View>

View File

@ -3,6 +3,7 @@ import { View, Text, StyleSheet, TextInput } from 'react-native';
import { Card } from './Card'; import { Card } from './Card';
import { Button } from './Button'; import { Button } from './Button';
import { Picker } from './Picker'; import { Picker } from './Picker';
import { ButtonGrid } from './ButtonGrid'; // NEW
import { colors, spacing, typography, borderRadius } from '../lib/theme'; import { colors, spacing, typography, borderRadius } from '../lib/theme';
import { GlazeLayer, ApplicationMethod, GlazePosition } from '../types'; import { GlazeLayer, ApplicationMethod, GlazePosition } from '../types';
import { getGlaze } from '../lib/db/repositories'; import { getGlaze } from '../lib/db/repositories';
@ -76,9 +77,8 @@ export const GlazeLayerCard: React.FC<GlazeLayerCardProps> = ({
<Text style={styles.glazeName}>{glazeName}</Text> <Text style={styles.glazeName}>{glazeName}</Text>
<Picker <Text style={styles.label}>Application Method</Text>
label="Application Method" <ButtonGrid
value={layer.application}
options={[ options={[
{ value: 'brush', label: 'Brushing' }, { value: 'brush', label: 'Brushing' },
{ value: 'dip', label: 'Dipping' }, { value: 'dip', label: 'Dipping' },
@ -86,12 +86,13 @@ export const GlazeLayerCard: React.FC<GlazeLayerCardProps> = ({
{ value: 'spray', label: 'Spraying' }, { value: 'spray', label: 'Spraying' },
{ value: 'other', label: 'Other' }, { value: 'other', label: 'Other' },
]} ]}
onValueChange={handleApplicationChange} selectedValue={layer.application}
onSelect={handleApplicationChange}
columns={2}
/> />
<Picker <Text style={styles.label}>Position on Piece</Text>
label="Position on Piece" <ButtonGrid
value={layer.position}
options={[ options={[
{ value: 'entire', label: 'Entire Piece' }, { value: 'entire', label: 'Entire Piece' },
{ value: 'top', label: 'Top' }, { value: 'top', label: 'Top' },
@ -101,7 +102,9 @@ export const GlazeLayerCard: React.FC<GlazeLayerCardProps> = ({
{ value: 'outside', label: 'Outside' }, { value: 'outside', label: 'Outside' },
{ value: 'other', label: 'Other' }, { value: 'other', label: 'Other' },
]} ]}
onValueChange={handlePositionChange} selectedValue={layer.position}
onSelect={handlePositionChange}
columns={2}
/> />
<View style={styles.coatsContainer}> <View style={styles.coatsContainer}>

View File

@ -111,16 +111,19 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
}; };
await AsyncStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(currentUser)); await AsyncStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(currentUser));
setUser(currentUser);
// Load onboarding status for returning user // Load onboarding status for returning user BEFORE setting user state
// This prevents the flickering of the Onboarding screen
const onboardingData = await AsyncStorage.getItem(ONBOARDING_STORAGE_KEY); const onboardingData = await AsyncStorage.getItem(ONBOARDING_STORAGE_KEY);
let isCompleted = false;
if (onboardingData) { if (onboardingData) {
const completedUsers = JSON.parse(onboardingData); const completedUsers = JSON.parse(onboardingData);
setHasCompletedOnboarding(completedUsers[currentUser.id] === true); isCompleted = completedUsers[currentUser.id] === true;
} else {
setHasCompletedOnboarding(false);
} }
// Update states together
setHasCompletedOnboarding(isCompleted);
setUser(currentUser);
} catch (error) { } catch (error) {
console.error('Sign in failed:', error); console.error('Sign in failed:', error);
throw error; throw error;

View File

@ -4,46 +4,46 @@
*/ */
export const colors = { export const colors = {
// Primary colors - Coral/Orange for buttons and accents // Primary colors - Sage/Olive green from new palette
primary: '#E07A5F', // Coral/Orange accent primary: '#8B9A7C', // Sage/Olive green
primaryLight: '#BF6B50', // Darker coral for cards/active states primaryLight: '#B0B1A5', // Lighter sage
primaryDark: '#C56548', // Darker coral for active states primaryDark: '#7A7B70', // Darker sage
// Accent colors - Green for status indicators // Accent colors - Beige/Sand from new palette
accent: '#537A2F', // Green accent accent: '#DECCA3', // Beige/Sand
accentLight: '#6B9A3E', // Lighter green accentLight: '#E8DCC0', // Lighter beige
accentDark: '#3F5D24', // Darker green accentDark: '#C9B78E', // Darker beige
// Backgrounds - Dark brown theme // Backgrounds - Light cream theme
background: '#3D352E', // Main dark brown background: '#F8F6F0', // Main cream/off-white
backgroundSecondary: '#4F4640', // Card/Surface warm brown backgroundSecondary: '#EEEEEE', // Light gray for cards
card: '#4F4640', // Same as backgroundSecondary card: '#FFFFFF', // White cards
// Text - Light on dark // Text - Dark on light backgrounds
text: '#D2CCC5', // Primary text (light warm) text: '#3D352E', // Primary text (dark brown)
textSecondary: '#BFC0C1', // Secondary text textSecondary: '#5A514B', // Secondary text
textTertiary: '#959693', // Dimmed text/icons textTertiary: '#7A7570', // Dimmed text/icons
buttonText: '#FFFFFF', // White text for primary buttons (better contrast) buttonText: '#FFFFFF', // White text for primary buttons
// Borders - Subtle on dark backgrounds // Borders - Subtle on light backgrounds
border: '#6B6460', // Slightly lighter than card for visibility border: '#DECCA3', // Beige border
borderLight: '#5A514B', // Chip background color borderLight: '#EEEEEE', // Light gray border
// Status colors // Status colors
success: '#537A2F', // Green accent (same as accent) success: '#7A9B6B', // Soft green
successLight: '#6B9A3E', // Lighter green background successLight: '#A3C494', // Lighter green
warning: '#E07A5F', // Use coral for warnings on dark bg warning: '#D4A574', // Warm amber
error: '#DC2626', // Keep red for errors (good contrast) error: '#C5675C', // Soft red
info: '#E07A5F', // Use coral for info info: '#9A9B8E', // Sage (same as primary)
// Step type colors - Adjusted for dark backgrounds // Step type colors - Adjusted for light backgrounds
forming: '#C89B7C', // Lighter clay tone forming: '#C89B7C', // Clay tone
trimming: '#A89080', // Neutral tan trimming: '#9A9B8E', // Sage green
drying: '#D9C5A0', // Lighter sand drying: '#DECCA3', // Beige/sand
bisqueFiring: '#D67B59', // Coral variant bisqueFiring: '#D4A574', // Warm amber
glazing: '#9B8B9B', // Lighter mauve glazing: '#A89B9B', // Mauve
glazeFiring: '#E07A5F', // Coral (same as primary) glazeFiring: '#C5675C', // Terracotta
misc: '#959693', // Same as textTertiary misc: '#7A7570', // Gray
}; };
export const spacing = { export const spacing = {
@ -125,13 +125,13 @@ export const stepTypeColors = {
}; };
export const stepTypeIcons: Record<string, string> = { export const stepTypeIcons: Record<string, string> = {
forming: '🏺', forming: 'hand-front-right',
trimming: '🔧', trimming: 'content-cut',
drying: '☀️', drying: 'weather-windy',
bisque_firing: '🔥', bisque_firing: 'fire',
glazing: '🎨', glazing: 'brush',
glaze_firing: '', glaze_firing: 'lightning-bolt',
misc: '📝', misc: 'notebook-outline',
}; };
export const stepTypeLabels: Record<string, string> = { export const stepTypeLabels: Record<string, string> = {

View File

@ -5,9 +5,9 @@ import { Step, StepType } from '../../types';
*/ */
const STEP_ORDER: StepType[] = [ const STEP_ORDER: StepType[] = [
'forming', 'forming',
'trimming',
'drying', 'drying',
'bisque_firing', 'bisque_firing',
'trimming',
'glazing', 'glazing',
'glaze_firing', 'glaze_firing',
'misc', 'misc',
@ -44,17 +44,30 @@ export function sortStepsByLogicalOrder(steps: Step[]): Step[] {
* Returns null if all typical steps are completed * Returns null if all typical steps are completed
*/ */
export function suggestNextStep(completedSteps: Step[]): StepType | null { export function suggestNextStep(completedSteps: Step[]): StepType | null {
if (completedSteps.length === 0) {
return 'forming';
}
// Find the highest step index completed so far
let highestIndex = -1;
const completedTypes = new Set(completedSteps.map(s => s.type)); const completedTypes = new Set(completedSteps.map(s => s.type));
// Find first step type not yet completed for (const stepType of completedTypes) {
for (const stepType of STEP_ORDER) { const index = getStepOrderIndex(stepType as StepType);
if (stepType === 'misc') continue; // Skip misc, it's optional if (index > highestIndex && index < 999) { // 999 is invalid/misc
if (!completedTypes.has(stepType)) { highestIndex = index;
return stepType;
} }
} }
return null; // All steps completed // Suggest the next step in the sequence
if (highestIndex !== -1 && highestIndex < STEP_ORDER.length - 1) {
const nextStepInfo = STEP_ORDER[highestIndex + 1];
if (nextStepInfo !== 'misc') {
return nextStepInfo;
}
}
return null;
} }
/** /**

View File

@ -136,18 +136,19 @@ function MainTabs() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tabBarContainer: { tabBarContainer: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.background, backgroundColor: '#FFFFFF',
height: 65, height: 65,
position: 'absolute', position: 'absolute',
bottom: 20, bottom: 20,
left: 10, left: 10,
right: 10, right: 10,
borderRadius: 25, borderRadius: 25,
shadowColor: colors.text, // Strong floating shadow
shadowOffset: { width: 0, height: -2 }, shadowColor: '#000000',
shadowOpacity: 0.1, shadowOffset: { width: 0, height: 8 },
shadowRadius: 8, shadowOpacity: 0.15,
elevation: 8, shadowRadius: 16,
elevation: 12,
}, },
tabItem: { tabItem: {
flex: 1, flex: 1,
@ -156,18 +157,31 @@ const styles = StyleSheet.create({
paddingVertical: 8, paddingVertical: 8,
}, },
tabItemActive: { tabItemActive: {
backgroundColor: colors.primaryLight, backgroundColor: '#DCDCDC', // Darker inner gray
borderWidth: 2, borderColor: '#FFFFFF', // Light outer border
borderColor: colors.primary, borderWidth: 3,
borderRadius: 18,
marginHorizontal: 5,
marginVertical: 1, // Maximized height
paddingTop: 1,
paddingBottom: 5, // Push text up away from border
// Refined shadow
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 3,
elevation: 3,
}, },
tabItemActiveFirst: { tabItemActiveFirst: {
borderRadius: 25, borderTopLeftRadius: 20,
borderBottomLeftRadius: 20,
}, },
tabItemActiveLast: { tabItemActiveLast: {
borderRadius: 25, borderTopRightRadius: 20,
borderBottomRightRadius: 20,
}, },
tabItemActiveMiddle: { tabItemActiveMiddle: {
borderRadius: 25, borderRadius: 18,
}, },
imageIconContainer: { imageIconContainer: {
width: 40, width: 40,
@ -245,14 +259,16 @@ export function AppNavigator() {
options={{ headerShown: false }} options={{ headerShown: false }}
/> />
</> </>
) : ( ) : !hasCompletedOnboarding ? (
// App screens - logged in (initial route determined by hasCompletedOnboarding) // Onboarding flow - explicit separation ensures it's not skipped
<>
<RootStack.Screen <RootStack.Screen
name="Onboarding" name="Onboarding"
component={OnboardingScreen} component={OnboardingScreen}
options={{ headerShown: false }} options={{ headerShown: false }}
/> />
) : (
// App screens - logged in AND onboarding completed
<>
<RootStack.Screen <RootStack.Screen
name="MainTabs" name="MainTabs"
component={MainTabs} component={MainTabs}

View File

@ -1,6 +1,6 @@
import { NavigatorScreenParams } from '@react-navigation/native'; import { NavigatorScreenParams } from '@react-navigation/native';
import React from 'react'; import React from 'react';
import { GlazeLayer, UserListType } from '../types'; import { GlazeLayer, UserListType, StepType } from '../types';
export type RootStackParamList = { export type RootStackParamList = {
Login: undefined; Login: undefined;
@ -14,6 +14,7 @@ export type RootStackParamList = {
selectedGlazeIds?: string[]; // DEPRECATED - keep for backwards compat selectedGlazeIds?: string[]; // DEPRECATED - keep for backwards compat
newGlazeLayers?: GlazeLayer[]; // NEW newGlazeLayers?: GlazeLayer[]; // NEW
mixNotes?: string; mixNotes?: string;
initialStepType?: StepType; // NEW
_timestamp?: number; _timestamp?: number;
_editorKey?: string _editorKey?: string
}; };

View File

@ -15,11 +15,25 @@ import { RootStackParamList } from '../navigation/types';
import { Project, Step } from '../types'; import { Project, Step } from '../types';
import { getAllProjects, getStepsByProject } from '../lib/db/repositories'; import { getAllProjects, getStepsByProject } from '../lib/db/repositories';
import { Card, PotteryIcon } from '../components'; import { Card, PotteryIcon } from '../components';
import { colors, spacing, typography, borderRadius, stepTypeLabels } from '../lib/theme'; import { colors, spacing, typography, borderRadius, stepTypeLabels, shadows } from '../lib/theme';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
const settingsIcon = require('../../assets/images/settings_icon.png'); const settingsIcon = require('../../assets/images/settings_icon.png');
const heroImages = [
require('../../assets/images/hero_pottery.jpg'),
require('../../assets/images/pottery_bg_1.png'),
require('../../assets/images/pottery_bg_2.png'),
require('../../assets/images/pottery_bg_3.png'),
require('../../assets/images/pottery_bg_4.png'),
require('../../assets/images/pottery_bg_5.png'), // NEW
require('../../assets/images/pottery_bg_6.png'), // NEW
require('../../assets/images/pottery_bg_7.png'), // NEW
];
// Select random image once when module loads (on app start/reload)
const randomHeroImage = heroImages[Math.floor(Math.random() * heroImages.length)];
type NavigationProp = NativeStackNavigationProp<RootStackParamList>; type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
export const HomeScreen: React.FC = () => { export const HomeScreen: React.FC = () => {
@ -83,7 +97,7 @@ export const HomeScreen: React.FC = () => {
// Calculate stats // Calculate stats
const totalProjects = projects.length; const totalProjects = projects.length;
const inProgressProjects = projects.filter(p => p.status === 'in_progress').length; const inProgressProjects = projects.filter(p => p.status === 'in_progress').length;
const completedProjects = projects.filter(p => p.status === 'completed').length; const completedProjects = projects.filter(p => p.status === 'done').length;
// Get recent projects (last 3) // Get recent projects (last 3)
const recentProjects = projects const recentProjects = projects
@ -115,39 +129,39 @@ export const HomeScreen: React.FC = () => {
} }
return ( return (
<SafeAreaView style={styles.container} edges={['top']}> <SafeAreaView style={styles.container} edges={[]}>
<ScrollView <ScrollView
style={styles.scrollView} style={styles.scrollView}
contentContainerStyle={styles.scrollContent} contentContainerStyle={styles.scrollContentNoPadding}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
> >
{/* Hero Section with Full-Width Edge-to-Edge Image */}
<View style={styles.heroSection}>
{/* Hero Image - edge to edge */}
<Image source={randomHeroImage} style={styles.heroImage} resizeMode="cover" />
{/* Content Overlay */}
<View style={styles.heroOverlay}>
{/* Top App Bar */} {/* Top App Bar */}
<View style={styles.topBar}> <View style={styles.topBar}>
<View style={styles.greetingContainer}> <View style={styles.greetingContainer}>
<Text style={styles.greetingText}>Welcome Back,</Text> <Text style={styles.greetingText}>Welcome Back,</Text>
<Text style={styles.nameText}>{user?.name || 'Potter'}</Text> <Text style={styles.nameText}>{user?.name || 'Potter'}</Text>
</View> </View>
<TouchableOpacity
style={styles.settingsButton}
onPress={() => navigation.navigate('MainTabs', { screen: 'Settings' })}
>
<Image
source={settingsIcon}
style={styles.settingsIconImage}
resizeMode="contain"
/>
</TouchableOpacity>
</View> </View>
{/* Stats Section */} {/* Stats */}
<View style={styles.statsContainer}> <View style={styles.statsContainer}>
<View style={styles.statCard}> <View style={styles.statsRow}>
<Text style={styles.statLabel}>Total Projects</Text> <View style={styles.statItem}>
<Text style={styles.statNumber}>{totalProjects}</Text> <Text style={styles.statNumber}>{totalProjects}</Text>
<Text style={styles.statLabel}>Projects</Text>
</View> </View>
<View style={styles.statCard}> <View style={styles.statItem}>
<Text style={styles.statLabel}>In Progress</Text>
<Text style={styles.statNumber}>{inProgressProjects}</Text> <Text style={styles.statNumber}>{inProgressProjects}</Text>
<Text style={styles.statLabel}>In Progress</Text>
</View>
</View>
</View>
</View> </View>
</View> </View>
@ -159,12 +173,6 @@ export const HomeScreen: React.FC = () => {
> >
<Text style={styles.primaryButtonText}>New Pottery Project</Text> <Text style={styles.primaryButtonText}>New Pottery Project</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity
style={styles.secondaryButton}
onPress={handleBrowseTips}
>
<Text style={styles.secondaryButtonText}>Browse Pottery Tips</Text>
</TouchableOpacity>
</View> </View>
{/* Section Header */} {/* Section Header */}
@ -248,12 +256,16 @@ const styles = StyleSheet.create({
paddingHorizontal: spacing.lg, paddingHorizontal: spacing.lg,
paddingBottom: 100, paddingBottom: 100,
}, },
scrollContentNoPadding: {
paddingBottom: 100,
},
topBar: { topBar: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'flex-start', justifyContent: 'flex-start',
alignItems: 'center', alignItems: 'center',
paddingVertical: spacing.lg, paddingTop: spacing.xl * 3,
marginBottom: spacing.lg, paddingHorizontal: spacing.lg,
marginBottom: spacing.md,
gap: spacing.lg, gap: spacing.lg,
}, },
greetingContainer: { greetingContainer: {
@ -279,40 +291,70 @@ const styles = StyleSheet.create({
height: 32, height: 32,
opacity: 0.8, opacity: 0.8,
}, },
statsContainer: { heroSection: {
flexDirection: 'row', position: 'relative',
gap: spacing.md, height: 380,
marginBottom: spacing.xl, marginBottom: spacing.xl,
}, },
statCard: { heroImage: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
opacity: 0.21,
},
heroOverlay: {
flex: 1, flex: 1,
backgroundColor: colors.card, },
padding: spacing.lg, statsContainer: {
borderRadius: borderRadius.lg, flex: 1,
borderWidth: 1, justifyContent: 'center',
borderColor: colors.border,
alignItems: 'center', alignItems: 'center',
paddingHorizontal: spacing.lg,
},
statsRow: {
flexDirection: 'row',
gap: spacing.xl * 2,
},
statItem: {
alignItems: 'center',
},
statNumber: {
fontSize: 48,
fontWeight: typography.fontWeight.bold,
color: '#8B9A7C', // Sage green from palette
textShadowColor: 'rgba(0, 0, 0, 0.2)',
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 3,
}, },
statLabel: { statLabel: {
fontSize: typography.fontSize.sm, fontSize: typography.fontSize.sm,
color: colors.textSecondary, color: colors.textSecondary,
marginBottom: spacing.xs, textTransform: 'uppercase',
}, letterSpacing: 0.5,
statNumber: { textShadowColor: 'rgba(0, 0, 0, 0.1)',
fontSize: 32, textShadowOffset: { width: 1, height: 1 },
fontWeight: typography.fontWeight.bold, textShadowRadius: 2,
color: colors.primary, fontWeight: '600',
}, },
buttonGroup: { buttonGroup: {
gap: spacing.md, gap: spacing.md,
marginBottom: spacing.xl, marginBottom: spacing.xl,
paddingHorizontal: spacing.lg,
}, },
primaryButton: { primaryButton: {
backgroundColor: colors.primaryLight, backgroundColor: '#8B9A7C', // Sage green from palette
paddingVertical: spacing.md, paddingVertical: spacing.md,
paddingHorizontal: spacing.lg, paddingHorizontal: spacing.lg,
borderRadius: borderRadius.lg, borderRadius: borderRadius.lg,
alignItems: 'center', alignItems: 'center',
// Floating effect
shadowColor: '#000000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
}, },
primaryButtonText: { primaryButtonText: {
fontSize: typography.fontSize.md, fontSize: typography.fontSize.md,
@ -338,6 +380,7 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
marginBottom: spacing.md, marginBottom: spacing.md,
paddingHorizontal: spacing.lg,
}, },
sectionTitle: { sectionTitle: {
fontSize: typography.fontSize.lg, fontSize: typography.fontSize.lg,
@ -351,6 +394,7 @@ const styles = StyleSheet.create({
}, },
projectsList: { projectsList: {
gap: spacing.md, gap: spacing.md,
paddingHorizontal: spacing.lg,
}, },
projectItem: { projectItem: {
flexDirection: 'row', flexDirection: 'row',
@ -359,8 +403,9 @@ const styles = StyleSheet.create({
backgroundColor: colors.card, backgroundColor: colors.card,
padding: spacing.md, padding: spacing.md,
borderRadius: borderRadius.lg, borderRadius: borderRadius.lg,
borderWidth: 1, borderWidth: 2,
borderColor: colors.border, borderColor: colors.border,
...shadows.md,
}, },
projectItemLeft: { projectItemLeft: {
flexDirection: 'row', flexDirection: 'row',

View File

@ -63,9 +63,11 @@ export const LoginScreen: React.FC = () => {
{/* Hero Image */} {/* Hero Image */}
<View style={styles.heroContainer}> <View style={styles.heroContainer}>
<View style={styles.heroImagePlaceholder}> <Image
<Text style={styles.heroEmoji}>🏺</Text> source={require('../../assets/images/app_logo.png')}
</View> style={styles.heroImage}
resizeMode="contain"
/>
</View> </View>
{/* Form */} {/* Form */}
@ -91,7 +93,7 @@ export const LoginScreen: React.FC = () => {
/> />
<TouchableOpacity <TouchableOpacity
style={styles.primaryButton} style={[styles.primaryButton, { marginTop: spacing.xl }]}
onPress={handleLogin} onPress={handleLogin}
disabled={loading} disabled={loading}
> >
@ -100,25 +102,8 @@ export const LoginScreen: React.FC = () => {
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
{/* Divider */}
<View style={styles.divider}>
<View style={styles.dividerLine} />
<Text style={styles.dividerText}>OR</Text>
<View style={styles.dividerLine} />
</View>
{/* Social Buttons */}
<View style={styles.socialButtons}>
<TouchableOpacity style={styles.socialButton}>
<Text style={styles.socialButtonText}>G</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.socialButton}>
<Text style={styles.socialButtonText}>🍎</Text>
</TouchableOpacity>
</View>
{/* Sign Up Link */} {/* Sign Up Link */}
<View style={styles.signupContainer}> <View style={[styles.signupContainer, { marginTop: spacing.xl }]}>
<Text style={styles.signupText}>Don't have an account? </Text> <Text style={styles.signupText}>Don't have an account? </Text>
<TouchableOpacity onPress={() => navigation.navigate('SignUp')}> <TouchableOpacity onPress={() => navigation.navigate('SignUp')}>
<Text style={styles.signupLink}>Create Account</Text> <Text style={styles.signupLink}>Create Account</Text>
@ -165,16 +150,10 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
marginVertical: spacing.xl * 1.5, marginVertical: spacing.xl * 1.5,
}, },
heroImagePlaceholder: { heroImage: {
width: 200, width: 180,
height: 200, height: 180,
borderRadius: borderRadius.lg, borderRadius: borderRadius.lg,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
},
heroEmoji: {
fontSize: 100,
}, },
form: { form: {
width: '100%', width: '100%',

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Card } from '../components'; import { Card } from '../components';
import { colors, spacing, typography, borderRadius } from '../lib/theme'; import { colors, spacing, typography, borderRadius } from '../lib/theme';
@ -9,7 +10,7 @@ interface Tip {
title: string; title: string;
content: string; content: string;
category: string; category: string;
icon: string; icon: keyof typeof MaterialCommunityIcons.glyphMap;
} }
const POTTERY_TIPS: Tip[] = [ const POTTERY_TIPS: Tip[] = [
@ -18,227 +19,243 @@ const POTTERY_TIPS: Tip[] = [
title: 'Wedging Clay', title: 'Wedging Clay',
content: 'Always wedge your clay thoroughly before throwing to remove air bubbles and ensure consistent texture. Aim for 50-100 wedges.', content: 'Always wedge your clay thoroughly before throwing to remove air bubbles and ensure consistent texture. Aim for 50-100 wedges.',
category: 'Basics', category: 'Basics',
icon: '🏺', icon: 'shape-plus',
}, },
{ {
id: '2', id: '2',
title: 'Drying Time', title: 'Drying Time',
content: 'Dry pieces slowly and evenly. Cover with plastic for controlled drying. Uneven drying leads to cracks!', content: 'Dry pieces slowly and evenly. Cover with plastic for controlled drying. Uneven drying leads to cracks!',
category: 'Drying', category: 'Drying',
icon: '☀️', icon: 'weather-sunny',
}, },
{ {
id: '3', id: '3',
title: 'Bisque Firing', title: 'Bisque Firing',
content: 'Bisque fire to Cone 04 (1945°F) for most clay bodies. This makes pieces porous and ready for glazing.', content: 'Bisque fire to Cone 04 (1945°F) for most clay bodies. This makes pieces porous and ready for glazing.',
category: 'Firing', category: 'Firing',
icon: '🔥', icon: 'fire',
}, },
{ {
id: '4', id: '4',
title: 'Glaze Application', title: 'Glaze Application',
content: 'Apply 2-3 coats of glaze for best results. Too thin = bare spots, too thick = running. Test on tiles first!', content: 'Apply 2-3 coats of glaze for best results. Too thin = bare spots, too thick = running. Test on tiles first!',
category: 'Glazing', category: 'Glazing',
icon: '🎨', icon: 'brush',
}, },
{ {
id: '5', id: '5',
title: 'Cone 6 is Popular', title: 'Cone 6 is Popular',
content: 'Cone 6 (2232°F) is the most common mid-range firing temperature. Great balance of durability and glaze options.', content: 'Cone 6 (2232°F) is the most common mid-range firing temperature. Great balance of durability and glaze options.',
category: 'Firing', category: 'Firing',
icon: '', icon: 'flash',
}, },
{ {
id: '6', id: '6',
title: 'Document Everything', title: 'Document Everything',
content: 'Keep detailed notes! Record clay type, glaze brands, cone numbers, and firing schedules. This app helps!', content: 'Keep detailed notes! Record clay type, glaze brands, cone numbers, and firing schedules. This app helps!',
category: 'Best Practice', category: 'Best Practice',
icon: '📝', icon: 'notebook-edit',
}, },
{ {
id: '7', id: '7',
title: 'Test Tiles', title: 'Test Tiles',
content: 'Always make test tiles for new glaze combinations. Save yourself from ruined pieces!', content: 'Always make test tiles for new glaze combinations. Save yourself from ruined pieces!',
category: 'Glazing', category: 'Glazing',
icon: '🧪', icon: 'flask',
}, },
{ {
id: '8', id: '8',
title: 'Thickness Matters', title: 'Thickness Matters',
content: 'Keep walls consistent - about 1/4 to 3/8 inch thick. Thinner walls fire more evenly.', content: 'Keep walls consistent - about 1/4 to 3/8 inch thick. Thinner walls fire more evenly.',
category: 'Forming', category: 'Forming',
icon: '📏', icon: 'ruler',
}, },
{ {
id: '9', id: '9',
title: 'Trimming Timing', title: 'Trimming Timing',
content: 'Trim when leather-hard (firm but not dry). Too wet = distorts, too dry = cracks.', content: 'Trim when leather-hard (firm but not dry). Too wet = distorts, too dry = cracks.',
category: 'Trimming', category: 'Trimming',
icon: '🔧', icon: 'tools',
}, },
{ {
id: '10', id: '10',
title: 'Foot Rings', title: 'Foot Rings',
content: 'Add a foot ring for stability and to elevate the piece from surfaces. Enhances the final look!', content: 'Add a foot ring for stability and to elevate the piece from surfaces. Enhances the final look!',
category: 'Trimming', category: 'Trimming',
icon: '', icon: 'circle-double',
}, },
{ {
id: '11', id: '11',
title: 'Reclaim Clay', title: 'Reclaim Clay',
content: 'Save all scraps! Dry completely, crush, add water, and wedge thoroughly to reuse. Nothing goes to waste.', content: 'Save all scraps! Dry completely, crush, add water, and wedge thoroughly to reuse. Nothing goes to waste.',
category: 'Recycling', category: 'Recycling',
icon: '♻️', icon: 'recycle',
}, },
{ {
id: '12', id: '12',
title: 'Never Mix Clay Bodies', title: 'Never Mix Clay Bodies',
content: 'Keep different clay types separate to avoid firing issues. Different clays shrink at different rates!', content: 'Keep different clay types separate to avoid firing issues. Different clays shrink at different rates!',
category: 'Recycling', category: 'Recycling',
icon: '⚠️', icon: 'alert-circle',
}, },
{ {
id: '13', id: '13',
title: 'Wire Tool', title: 'Wire Tool',
content: 'Use a twisted wire for cutting clay. Keep it taut for clean cuts and remove pieces from the wheel.', content: 'Use a twisted wire for cutting clay. Keep it taut for clean cuts and remove pieces from the wheel.',
category: 'Tools', category: 'Tools',
icon: '✂️', icon: 'content-cut',
}, },
{ {
id: '14', id: '14',
title: 'Sponge Control', title: 'Sponge Control',
content: 'Don\'t over-sponge! Too much water weakens your piece and can cause collapse. Light touch is key.', content: 'Don\'t over-sponge! Too much water weakens your piece and can cause collapse. Light touch is key.',
category: 'Tools', category: 'Tools',
icon: '🧽', icon: 'water',
}, },
{ {
id: '15', id: '15',
title: 'Underglaze vs Glaze', title: 'Underglaze vs Glaze',
content: 'Underglazes go on greenware or bisque, glazes only on bisque. Knowing the difference prevents mistakes!', content: 'Underglazes go on greenware or bisque, glazes only on bisque. Knowing the difference prevents mistakes!',
category: 'Surface', category: 'Surface',
icon: '🖌️', icon: 'brush-variant',
}, },
{ {
id: '16', id: '16',
title: 'Sgraffito Timing', title: 'Sgraffito Timing',
content: 'Scratch through slip or underglaze when leather-hard for crisp, clean lines. Sharp tools work best!', content: 'Scratch through slip or underglaze when leather-hard for crisp, clean lines. Sharp tools work best!',
category: 'Surface', category: 'Surface',
icon: '✏️', icon: 'pencil',
}, },
{ {
id: '17', id: '17',
title: 'Compression', title: 'Compression',
content: 'Compress the bottom of thrown pieces to prevent cracking. Press firmly while centering to strengthen clay.', content: 'Compress the bottom of thrown pieces to prevent cracking. Press firmly while centering to strengthen clay.',
category: 'Forming', category: 'Forming',
icon: '💪', icon: 'weight',
}, },
{ {
id: '18', id: '18',
title: 'S-Cracks Prevention', title: 'S-Cracks Prevention',
content: 'Pull from center outward when throwing to avoid S-cracks in the bottom. Consistent technique is crucial!', content: 'Pull from center outward when throwing to avoid S-cracks in the bottom. Consistent technique is crucial!',
category: 'Forming', category: 'Forming',
icon: '🌀', icon: 'rotate-3d-variant',
}, },
{ {
id: '19', id: '19',
title: 'Silica Dust Safety', title: 'Silica Dust Safety',
content: 'Always wet-clean clay dust. Never sweep dry - silica dust is harmful to lungs! Your health matters.', content: 'Always wet-clean clay dust. Never sweep dry - silica dust is harmful to lungs! Your health matters.',
category: 'Safety', category: 'Safety',
icon: '🫁', icon: 'air-filter',
}, },
{ {
id: '20', id: '20',
title: 'Ventilation', title: 'Ventilation',
content: 'Ensure good airflow when glazing and firing. Fumes can be toxic. Open windows or use exhaust fans!', content: 'Ensure good airflow when glazing and firing. Fumes can be toxic. Open windows or use exhaust fans!',
category: 'Safety', category: 'Safety',
icon: '🌬️', icon: 'weather-windy',
}, },
{ {
id: '21', id: '21',
title: 'Centering is Key', title: 'Centering is Key',
content: 'Take your time centering. A well-centered piece prevents wobbling and uneven walls throughout the throwing process.', content: 'Take your time centering. A well-centered piece prevents wobbling and uneven walls throughout the throwing process.',
category: 'Basics', category: 'Basics',
icon: '🎯', icon: 'target',
}, },
{ {
id: '22', id: '22',
title: 'Coning Up & Down', title: 'Coning Up & Down',
content: 'Cone clay up and down 2-3 times to align clay particles and strengthen the clay body before forming.', content: 'Cone clay up and down 2-3 times to align clay particles and strengthen the clay body before forming.',
category: 'Basics', category: 'Basics',
icon: '🔄', icon: 'arrow-up-down',
}, },
{ {
id: '23', id: '23',
title: 'Slow Bisque Start', title: 'Slow Bisque Start',
content: 'Start bisque firing slowly (150°F/hr to 400°F) to allow water to escape without cracking your pieces.', content: 'Start bisque firing slowly (150°F/hr to 400°F) to allow water to escape without cracking your pieces.',
category: 'Firing', category: 'Firing',
icon: '🐌', icon: 'speedometer-slow',
}, },
{ {
id: '24', id: '24',
title: 'Pyrometric Cones', title: 'Pyrometric Cones',
content: 'Always use witness cones! Digital controllers can fail. Cones don\'t lie about actual heatwork achieved.', content: 'Always use witness cones! Digital controllers can fail. Cones don\'t lie about actual heatwork achieved.',
category: 'Firing', category: 'Firing',
icon: '🎗️', icon: 'cone',
}, },
{ {
id: '25', id: '25',
title: 'Kiln Loading', title: 'Kiln Loading',
content: 'Stack bisque with 1" spacing for proper airflow. Touching pieces can stick together or crack during firing.', content: 'Stack bisque with 1" spacing for proper airflow. Touching pieces can stick together or crack during firing.',
category: 'Firing', category: 'Firing',
icon: '📦', icon: 'view-grid',
}, },
{ {
id: '26', id: '26',
title: 'Glaze Thickness Test', title: 'Glaze Thickness Test',
content: 'Dip a finger in wet glaze - you should see skin tone through it. Too opaque means it\'s too thick!', content: 'Dip a finger in wet glaze - you should see skin tone through it. Too opaque means it\'s too thick!',
category: 'Glazing', category: 'Glazing',
icon: '👆', icon: 'gesture-tap',
}, },
{ {
id: '27', id: '27',
title: 'Wax Resist', title: 'Wax Resist',
content: 'Wax the bottom of pieces before glazing to prevent them from sticking to kiln shelves. Essential step!', content: 'Wax the bottom of pieces before glazing to prevent them from sticking to kiln shelves. Essential step!',
category: 'Glazing', category: 'Glazing',
icon: '🕯️', icon: 'candle',
}, },
{ {
id: '28', id: '28',
title: 'Stir Your Glaze', title: 'Stir Your Glaze',
content: 'Always stir glazes thoroughly before use! Glaze settles and consistency directly affects color and texture.', content: 'Always stir glazes thoroughly before use! Glaze settles and consistency directly affects color and texture.',
category: 'Glazing', category: 'Glazing',
icon: '🥄', icon: 'spoon-sugar',
}, },
{ {
id: '29', id: '29',
title: 'Clay Storage', title: 'Clay Storage',
content: 'Store clay in airtight containers or wrap tightly in plastic. Add a damp towel for extra moisture retention.', content: 'Store clay in airtight containers or wrap tightly in plastic. Add a damp towel for extra moisture retention.',
category: 'Best Practice', category: 'Best Practice',
icon: '📦', icon: 'package-variant-closed',
}, },
{ {
id: '30', id: '30',
title: 'Clean Water', title: 'Clean Water',
content: 'Change throwing water frequently. Dirty water contaminates your clay with sediment and weakens it.', content: 'Change throwing water frequently. Dirty water contaminates your clay with sediment and weakens it.',
category: 'Best Practice', category: 'Best Practice',
icon: '💧', icon: 'water-check',
}, },
{ {
id: '31', id: '31',
title: 'Handle Attachment', title: 'Handle Attachment',
content: 'Attach handles to leather-hard pieces. Score surfaces, apply slip, and compress joints firmly for strength.', content: 'Attach handles to leather-hard pieces. Score surfaces, apply slip, and compress joints firmly for strength.',
category: 'Forming', category: 'Forming',
icon: '🤝', icon: 'handshake',
}, },
{ {
id: '32', id: '32',
title: 'Warping Prevention', title: 'Warping Prevention',
content: 'Dry thick and thin sections at the same rate by covering thinner areas with plastic. Prevents warping!', content: 'Dry thick and thin sections at the same rate by covering thinner areas with plastic. Prevents warping!',
category: 'Drying', category: 'Drying',
icon: '🛡️', icon: 'shield-outline',
}, },
]; ];
const categoryColors: Record<string, string> = {
'Basics': colors.textSecondary,
'Drying': colors.drying,
'Firing': colors.glazeFiring,
'Glazing': colors.glazing,
'Forming': colors.forming,
'Trimming': colors.trimming,
'Recycling': colors.success,
'Tools': colors.accentDark,
'Surface': colors.glazing,
'Safety': colors.error,
'Best Practice': colors.primary,
};
export const NewsScreen: React.FC = () => { export const NewsScreen: React.FC = () => {
const [selectedCategory, setSelectedCategory] = useState<string | null>(null); const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
@ -288,10 +305,12 @@ export const NewsScreen: React.FC = () => {
</View> </View>
<ScrollView style={styles.content} contentContainerStyle={styles.listContent}> <ScrollView style={styles.content} contentContainerStyle={styles.listContent}>
{filteredTips.map((tip) => ( {filteredTips.map((tip) => {
const iconColor = categoryColors[tip.category] || colors.primary;
return (
<Card key={tip.id} style={styles.tipCard}> <Card key={tip.id} style={styles.tipCard}>
<View style={styles.tipHeader}> <View style={styles.tipHeader}>
<Text style={styles.tipIcon}>{tip.icon}</Text> <MaterialCommunityIcons name={tip.icon} size={32} color={iconColor} />
<View style={styles.tipHeaderText}> <View style={styles.tipHeaderText}>
<Text style={styles.tipTitle}>{tip.title}</Text> <Text style={styles.tipTitle}>{tip.title}</Text>
<Text style={styles.tipCategory}>{tip.category}</Text> <Text style={styles.tipCategory}>{tip.category}</Text>
@ -299,7 +318,8 @@ export const NewsScreen: React.FC = () => {
</View> </View>
<Text style={styles.tipContent}>{tip.content}</Text> <Text style={styles.tipContent}>{tip.content}</Text>
</Card> </Card>
))} );
})}
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
); );
@ -374,9 +394,6 @@ const styles = StyleSheet.create({
marginBottom: spacing.md, marginBottom: spacing.md,
gap: spacing.md, gap: spacing.md,
}, },
tipIcon: {
fontSize: 32,
},
tipHeaderText: { tipHeaderText: {
flex: 1, flex: 1,
}, },

View File

@ -46,8 +46,8 @@ export const OnboardingScreen: React.FC = () => {
const handleComplete = async () => { const handleComplete = async () => {
// Save onboarding completion status // Save onboarding completion status
// This will trigger the AppNavigator to switch to MainTabs automatically
await completeOnboarding(); await completeOnboarding();
navigation.replace('MainTabs', { screen: 'Projects' });
}; };
const slide = SLIDES[currentSlide]; const slide = SLIDES[currentSlide];

View File

@ -10,6 +10,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import * as ImagePicker from 'expo-image-picker'; import * as ImagePicker from 'expo-image-picker';
import { MaterialCommunityIcons } from '@expo/vector-icons'; // NEW
import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../navigation/types'; import { RootStackParamList } from '../navigation/types';
@ -22,7 +23,7 @@ import {
getStepsByProject, getStepsByProject,
} from '../lib/db/repositories'; } from '../lib/db/repositories';
import { Button, Input, Card } from '../components'; import { Button, Input, Card } from '../components';
import { colors, spacing, typography, stepTypeIcons, stepTypeLabels, borderRadius } from '../lib/theme'; import { colors, spacing, typography, stepTypeIcons, stepTypeLabels, borderRadius, stepTypeColors, shadows } from '../lib/theme';
import { formatDate } from '../lib/utils/datetime'; import { formatDate } from '../lib/utils/datetime';
import { sortStepsByLogicalOrder, suggestNextStep } from '../lib/utils/stepOrdering'; import { sortStepsByLogicalOrder, suggestNextStep } from '../lib/utils/stepOrdering';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
@ -57,9 +58,15 @@ export const ProjectDetailScreen: React.FC = () => {
console.log('ProjectDetail focused - reloading steps'); console.log('ProjectDetail focused - reloading steps');
loadSteps(); loadSteps();
} }
}, [route.params.projectId, isNew]) }, [isNew])
); );
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
});
}, [navigation]);
const loadSteps = async () => { const loadSteps = async () => {
try { try {
const projectSteps = await getStepsByProject(route.params.projectId); const projectSteps = await getStepsByProject(route.params.projectId);
@ -216,6 +223,14 @@ export const ProjectDetailScreen: React.FC = () => {
return ( return (
<SafeAreaView style={styles.safeArea} edges={['top']}> <SafeAreaView style={styles.safeArea} edges={['top']}>
<View style={styles.header}>
<TouchableOpacity style={styles.backButton} onPress={() => navigation.goBack()}>
<MaterialCommunityIcons name="chevron-left" size={24} color={colors.text} />
<Text style={styles.backButtonText}>Back</Text>
</TouchableOpacity>
<Text style={styles.headerTitle}>Project Overview</Text>
</View>
<KeyboardAwareScrollView <KeyboardAwareScrollView
style={styles.container} style={styles.container}
contentContainerStyle={styles.content} contentContainerStyle={styles.content}
@ -231,7 +246,7 @@ export const ProjectDetailScreen: React.FC = () => {
<Image source={{ uri: coverImage }} style={styles.coverImagePreview} /> <Image source={{ uri: coverImage }} style={styles.coverImagePreview} />
) : ( ) : (
<View style={styles.imagePlaceholder}> <View style={styles.imagePlaceholder}>
<Text style={styles.imagePlaceholderText}>📷</Text> <MaterialCommunityIcons name="camera-plus-outline" size={48} color={colors.textSecondary} />
<Text style={styles.imagePlaceholderLabel}>Add Cover Photo</Text> <Text style={styles.imagePlaceholderLabel}>Add Cover Photo</Text>
</View> </View>
)} )}
@ -258,6 +273,7 @@ export const ProjectDetailScreen: React.FC = () => {
styles.statusButton, styles.statusButton,
styles.statusButtonInProgress, styles.statusButtonInProgress,
status === 'in_progress' && styles.statusButtonActive, status === 'in_progress' && styles.statusButtonActive,
status === 'in_progress' && styles.statusButtonInProgressActive,
]} ]}
onPress={() => setStatus('in_progress')} onPress={() => setStatus('in_progress')}
> >
@ -273,6 +289,7 @@ export const ProjectDetailScreen: React.FC = () => {
styles.statusButton, styles.statusButton,
styles.statusButtonDone, styles.statusButtonDone,
status === 'done' && styles.statusButtonActive, status === 'done' && styles.statusButtonActive,
status === 'done' && styles.statusButtonDoneActive,
]} ]}
onPress={() => setStatus('done')} onPress={() => setStatus('done')}
> >
@ -289,6 +306,7 @@ export const ProjectDetailScreen: React.FC = () => {
title={isNew ? 'Create Project' : 'Save Changes'} title={isNew ? 'Create Project' : 'Save Changes'}
onPress={handleSave} onPress={handleSave}
loading={saving} loading={saving}
style={styles.sageButton}
/> />
</Card> </Card>
@ -298,24 +316,34 @@ export const ProjectDetailScreen: React.FC = () => {
<Text style={styles.sectionTitle}>Process Steps</Text> <Text style={styles.sectionTitle}>Process Steps</Text>
<Button <Button
title="Add Step" title="Add Step"
icon={<MaterialCommunityIcons name="plus" size={20} color={colors.buttonText} />}
onPress={handleAddStep} onPress={handleAddStep}
size="sm" size="sm"
style={styles.sageButton}
/> />
</View> </View>
{steps.length > 0 && status === 'in_progress' && (() => { {steps.length > 0 && status === 'in_progress' && (() => {
const nextStep = suggestNextStep(steps); const nextStep = suggestNextStep(steps);
return nextStep ? ( return nextStep ? (
<Card style={styles.suggestionCard}> <TouchableOpacity
activeOpacity={0.8}
onPress={() => navigation.navigate('StepEditor', {
projectId: route.params.projectId,
initialStepType: nextStep
})}
>
<Card style={[styles.suggestionCard, { backgroundColor: '#F0F4E8' }]}>
<View style={styles.suggestionHeader}> <View style={styles.suggestionHeader}>
<Text style={styles.suggestionIcon}>💡</Text> <MaterialCommunityIcons name="lightbulb-on-outline" size={20} color={colors.primaryDark} />
<Text style={styles.suggestionTitle}>Suggested Next Step</Text> <Text style={[styles.suggestionTitle, { color: colors.primaryDark }]}>Suggested Next Step</Text>
</View> </View>
<View style={styles.suggestionContent}> <View style={styles.suggestionContent}>
<Text style={styles.suggestionStepIcon}>{stepTypeIcons[nextStep]}</Text> <MaterialCommunityIcons name={stepTypeIcons[nextStep] as any} size={24} color={stepTypeColors[nextStep]} />
<Text style={styles.suggestionStepLabel}>{stepTypeLabels[nextStep]}</Text> <Text style={[styles.suggestionStepLabel, { color: colors.text }]}>{stepTypeLabels[nextStep]}</Text>
</View> </View>
</Card> </Card>
</TouchableOpacity>
) : null; ) : null;
})()} })()}
@ -330,7 +358,7 @@ export const ProjectDetailScreen: React.FC = () => {
{/* Timeline dot and line */} {/* Timeline dot and line */}
<View style={styles.timelineLeft}> <View style={styles.timelineLeft}>
<View style={styles.timelineDot}> <View style={styles.timelineDot}>
<Text style={styles.timelineDotIcon}>{stepTypeIcons[step.type]}</Text> <MaterialCommunityIcons name={stepTypeIcons[step.type] as any} size={20} color={colors.background} />
</View> </View>
{index < steps.length - 1 && <View style={styles.timelineLine} />} {index < steps.length - 1 && <View style={styles.timelineLine} />}
</View> </View>
@ -367,7 +395,8 @@ export const ProjectDetailScreen: React.FC = () => {
title="Delete Project" title="Delete Project"
onPress={handleDelete} onPress={handleDelete}
variant="outline" variant="outline"
style={styles.deleteButton} style={styles.terracottaButton}
textStyle={styles.terracottaText}
/> />
</> </>
)} )}
@ -379,7 +408,40 @@ export const ProjectDetailScreen: React.FC = () => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
safeArea: { safeArea: {
flex: 1, flex: 1,
backgroundColor: colors.background, backgroundColor: colors.backgroundSecondary,
},
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: spacing.md,
paddingVertical: spacing.md,
backgroundColor: colors.backgroundSecondary,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
headerTitle: {
flex: 1,
textAlign: 'center',
fontSize: 24,
fontWeight: '800',
color: colors.text,
},
backButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.card,
borderRadius: borderRadius.full,
paddingVertical: spacing.xs,
paddingHorizontal: spacing.sm,
paddingRight: spacing.md,
...shadows.sm,
zIndex: 10, // Ensure it sits above the absolute title
},
backButtonText: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.medium,
color: colors.text,
marginLeft: -4,
}, },
container: { container: {
flex: 1, flex: 1,
@ -545,9 +607,15 @@ const styles = StyleSheet.create({
statusButtonInProgress: { statusButtonInProgress: {
borderColor: colors.primary, borderColor: colors.primary,
}, },
statusButtonInProgressActive: {
backgroundColor: '#E8E9E4', // Light sage tint
},
statusButtonDone: { statusButtonDone: {
borderColor: colors.success, borderColor: colors.success,
}, },
statusButtonDoneActive: {
backgroundColor: '#D4E8C8', // Light green tint
},
statusButtonArchived: { statusButtonArchived: {
borderColor: colors.textTertiary, borderColor: colors.textTertiary,
}, },
@ -598,4 +666,15 @@ const styles = StyleSheet.create({
fontWeight: typography.fontWeight.bold, fontWeight: typography.fontWeight.bold,
color: colors.text, color: colors.text,
}, },
sageButton: {
backgroundColor: '#8B9A7C',
borderColor: '#7A896C',
},
terracottaButton: {
borderColor: '#C5675C',
backgroundColor: 'transparent',
},
terracottaText: {
color: '#C5675C',
},
}); });

View File

@ -144,6 +144,7 @@ export const ProjectsScreen: React.FC = () => {
description="Start your first piece!" description="Start your first piece!"
actionLabel="New Project" actionLabel="New Project"
onAction={handleCreateProject} onAction={handleCreateProject}
buttonStyle={styles.sageButton}
/> />
) : ( ) : (
<FlatList <FlatList
@ -169,6 +170,7 @@ export const ProjectsScreen: React.FC = () => {
title="New Project" title="New Project"
onPress={handleCreateProject} onPress={handleCreateProject}
size="md" size="md"
style={styles.sageButton}
accessibilityLabel="Create new project" accessibilityLabel="Create new project"
/> />
</View> </View>
@ -327,5 +329,15 @@ const styles = StyleSheet.create({
gap: spacing.md, gap: spacing.md,
paddingHorizontal: spacing.lg, paddingHorizontal: spacing.lg,
}, },
sageButton: {
backgroundColor: '#8B9A7C',
borderColor: '#7A896C',
// Floating effect
shadowColor: '#000000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
},
}); });

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView, Switch, Alert, Share } from 'react-native'; import { View, Text, StyleSheet, ScrollView, Switch, Alert, Share, TouchableOpacity } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
@ -7,7 +7,7 @@ import { RootStackParamList } from '../navigation/types';
import { getSettings, updateSettings, getAllProjects, getStepsByProject, getListItems } from '../lib/db/repositories'; import { getSettings, updateSettings, getAllProjects, getStepsByProject, getListItems } from '../lib/db/repositories';
import { Settings } from '../types'; import { Settings } from '../types';
import { Card, Button } from '../components'; import { Card, Button } from '../components';
import { colors, spacing, typography } from '../lib/theme'; import { colors, spacing, typography, borderRadius } from '../lib/theme';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
type NavigationProp = NativeStackNavigationProp<RootStackParamList>; type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
@ -18,6 +18,7 @@ export const SettingsScreen: React.FC = () => {
const [settings, setSettings] = useState<Settings>({ const [settings, setSettings] = useState<Settings>({
unitSystem: 'imperial', unitSystem: 'imperial',
tempUnit: 'F', tempUnit: 'F',
weightUnit: 'lb',
analyticsOptIn: false, analyticsOptIn: false,
}); });
@ -120,59 +121,63 @@ export const SettingsScreen: React.FC = () => {
<Text style={styles.sectionTitle}>Units</Text> <Text style={styles.sectionTitle}>Units</Text>
<View style={styles.setting}> <View style={styles.setting}>
<View> <View style={styles.settingHeader}>
<Text style={styles.settingLabel}>Temperature Unit</Text> <Text style={styles.settingLabel}>Temperature Unit</Text>
<Text style={styles.settingValue}>
{settings.tempUnit === 'F' ? 'Fahrenheit (°F)' : 'Celsius (°C)'}
</Text>
</View> </View>
<Switch <View style={styles.toggleContainer}>
value={settings.tempUnit === 'C'} <TouchableOpacity
onValueChange={(value) => handleToggle('tempUnit', value ? 'C' : 'F')} style={[styles.toggleButton, settings.tempUnit === 'F' && styles.toggleButtonActive]}
trackColor={{ false: colors.border, true: colors.primary }} onPress={() => handleToggle('tempUnit', 'F')}
thumbColor={settings.tempUnit === 'C' ? colors.background : colors.background} >
ios_backgroundColor={colors.border} <Text style={[styles.toggleText, settings.tempUnit === 'F' && styles.toggleTextActive]}>°F</Text>
/> </TouchableOpacity>
<TouchableOpacity
style={[styles.toggleButton, settings.tempUnit === 'C' && styles.toggleButtonActive]}
onPress={() => handleToggle('tempUnit', 'C')}
>
<Text style={[styles.toggleText, settings.tempUnit === 'C' && styles.toggleTextActive]}>°C</Text>
</TouchableOpacity>
</View>
</View> </View>
<View style={styles.setting}> <View style={styles.setting}>
<View> <View style={styles.settingHeader}>
<Text style={styles.settingLabel}>Unit System</Text> <Text style={styles.settingLabel}>Unit System</Text>
<Text style={styles.settingValue}>
{settings.unitSystem === 'imperial' ? 'Imperial (lb/in)' : 'Metric (kg/cm)'}
</Text>
</View> </View>
<Switch <View style={styles.toggleContainer}>
value={settings.unitSystem === 'metric'} <TouchableOpacity
onValueChange={(value) => style={[styles.toggleButton, settings.unitSystem === 'imperial' && styles.toggleButtonActive]}
handleToggle('unitSystem', value ? 'metric' : 'imperial') onPress={() => handleToggle('unitSystem', 'imperial')}
} >
trackColor={{ false: colors.border, true: colors.primary }} <Text style={[styles.toggleText, settings.unitSystem === 'imperial' && styles.toggleTextActive]}>Imperial</Text>
thumbColor={settings.unitSystem === 'metric' ? colors.background : colors.background} </TouchableOpacity>
ios_backgroundColor={colors.border} <TouchableOpacity
/> style={[styles.toggleButton, settings.unitSystem === 'metric' && styles.toggleButtonActive]}
onPress={() => handleToggle('unitSystem', 'metric')}
>
<Text style={[styles.toggleText, settings.unitSystem === 'metric' && styles.toggleTextActive]}>Metric</Text>
</TouchableOpacity>
</View>
</View> </View>
<View style={[styles.setting, styles.noBorder]}> <View style={[styles.setting, styles.noBorder]}>
<View> <View style={styles.settingHeader}>
<Text style={styles.settingLabel}>Weight Unit</Text> <Text style={styles.settingLabel}>Weight Unit</Text>
<Text style={styles.settingValue}>
{settings.weightUnit === 'lb' ? 'Pounds (lb)' :
settings.weightUnit === 'oz' ? 'Ounces (oz)' :
settings.weightUnit === 'kg' ? 'Kilograms (kg)' : 'Grams (g)'}
</Text>
</View> </View>
<Switch <View style={styles.toggleContainer}>
value={settings.weightUnit === 'kg' || settings.weightUnit === 'g'} <TouchableOpacity
onValueChange={(value) => { style={[styles.toggleButton, (settings.weightUnit === 'lb' || settings.weightUnit === 'oz') && styles.toggleButtonActive]}
// Toggle between imperial (lb) and metric (kg) onPress={() => handleToggle('weightUnit', 'lb')}
const newUnit = value ? 'kg' : 'lb'; >
handleToggle('weightUnit', newUnit); <Text style={[styles.toggleText, (settings.weightUnit === 'lb' || settings.weightUnit === 'oz') && styles.toggleTextActive]}>lb</Text>
}} </TouchableOpacity>
trackColor={{ false: colors.border, true: colors.primary }} <TouchableOpacity
thumbColor={(settings.weightUnit === 'kg' || settings.weightUnit === 'g') ? colors.background : colors.background} style={[styles.toggleButton, (settings.weightUnit === 'kg' || settings.weightUnit === 'g') && styles.toggleButtonActive]}
ios_backgroundColor={colors.border} onPress={() => handleToggle('weightUnit', 'kg')}
/> >
<Text style={[styles.toggleText, (settings.weightUnit === 'kg' || settings.weightUnit === 'g') && styles.toggleTextActive]}>kg</Text>
</TouchableOpacity>
</View>
</View> </View>
</Card> </Card>
@ -283,9 +288,8 @@ const styles = StyleSheet.create({
letterSpacing: 0.5, letterSpacing: 0.5,
}, },
setting: { setting: {
flexDirection: 'row', flexDirection: 'column',
justifyContent: 'space-between', alignItems: 'flex-start',
alignItems: 'center',
paddingVertical: spacing.md, paddingVertical: spacing.md,
borderBottomWidth: 2, borderBottomWidth: 2,
borderBottomColor: colors.border, borderBottomColor: colors.border,
@ -297,9 +301,16 @@ const styles = StyleSheet.create({
}, },
settingValue: { settingValue: {
fontSize: typography.fontSize.sm, fontSize: typography.fontSize.sm,
color: colors.textSecondary,
marginTop: spacing.xs, marginTop: spacing.xs,
}, },
settingHeader: {
marginBottom: spacing.sm,
width: '100%',
},
settingInfo: {
flex: 1,
paddingRight: spacing.sm,
},
infoRow: { infoRow: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@ -341,6 +352,35 @@ const styles = StyleSheet.create({
listButton: { listButton: {
marginTop: spacing.sm, marginTop: spacing.sm,
}, },
toggleContainer: {
flexDirection: 'row',
backgroundColor: colors.background,
borderRadius: borderRadius.md,
padding: 0,
borderWidth: 1,
borderColor: colors.border,
width: '100%',
overflow: 'hidden',
},
toggleButton: {
flex: 1,
alignItems: 'center',
paddingHorizontal: spacing.sm,
paddingVertical: spacing.sm,
borderRadius: borderRadius.md,
},
toggleButtonActive: {
backgroundColor: colors.primary,
},
toggleText: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
fontWeight: typography.fontWeight.medium,
},
toggleTextActive: {
color: colors.buttonText,
fontWeight: typography.fontWeight.bold,
},
noBorder: { noBorder: {
borderBottomWidth: 0, borderBottomWidth: 0,
}, },

View File

@ -8,6 +8,7 @@ import {
Platform, Platform,
Alert, Alert,
ScrollView, ScrollView,
Image,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
@ -82,40 +83,15 @@ export const SignUpScreen: React.FC = () => {
{/* Hero Image */} {/* Hero Image */}
<View style={styles.heroContainer}> <View style={styles.heroContainer}>
<View style={styles.heroImagePlaceholder}> <Image
<Text style={styles.heroEmoji}>🏺</Text> source={require('../../assets/images/app_logo.png')}
</View> style={styles.heroImage}
resizeMode="contain"
/>
</View> </View>
{/* Form */} {/* Form */}
<View style={styles.form}> <View style={styles.form}>
<TouchableOpacity
style={styles.primaryButton}
onPress={handleSignUp}
disabled={loading}
>
<Text style={styles.primaryButtonText}>
{loading ? 'Creating Account...' : 'Create Account'}
</Text>
</TouchableOpacity>
{/* Divider */}
<View style={styles.divider}>
<View style={styles.dividerLine} />
<Text style={styles.dividerText}>OR</Text>
<View style={styles.dividerLine} />
</View>
{/* Social Buttons */}
<View style={styles.socialButtons}>
<TouchableOpacity style={styles.socialButton}>
<Text style={styles.socialButtonText}>G</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.socialButton}>
<Text style={styles.socialButtonText}>🍎</Text>
</TouchableOpacity>
</View>
{/* Input Fields */} {/* Input Fields */}
<Input <Input
label="Name" label="Name"
@ -156,8 +132,19 @@ export const SignUpScreen: React.FC = () => {
autoComplete="password" autoComplete="password"
/> />
{/* Create Account Button */}
<TouchableOpacity
style={[styles.primaryButton, { marginTop: spacing.xl }]}
onPress={handleSignUp}
disabled={loading}
>
<Text style={styles.primaryButtonText}>
{loading ? 'Creating Account...' : 'Create Account'}
</Text>
</TouchableOpacity>
{/* Login Link */} {/* Login Link */}
<View style={styles.loginContainer}> <View style={[styles.loginContainer, { marginTop: spacing.md }]}>
<Text style={styles.loginText}>Already have an account? </Text> <Text style={styles.loginText}>Already have an account? </Text>
<TouchableOpacity onPress={() => navigation.navigate('Login')}> <TouchableOpacity onPress={() => navigation.navigate('Login')}>
<Text style={styles.loginLink}>Log In</Text> <Text style={styles.loginLink}>Log In</Text>
@ -203,16 +190,10 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
marginVertical: spacing.xl, marginVertical: spacing.xl,
}, },
heroImagePlaceholder: { heroImage: {
width: 180, width: 180,
height: 180, height: 180,
borderRadius: borderRadius.lg, borderRadius: borderRadius.lg,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
},
heroEmoji: {
fontSize: 90,
}, },
form: { form: {
width: '100%', width: '100%',

View File

@ -1,9 +1,10 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { View, Text, StyleSheet, Alert, Image, TouchableOpacity, FlatList, TextInput, findNodeHandle } from 'react-native'; import { View, Text, StyleSheet, Alert, Image, TouchableOpacity, TextInput } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import * as ImagePicker from 'expo-image-picker'; import * as ImagePicker from 'expo-image-picker';
import { useNavigation, useRoute, RouteProp, CommonActions } from '@react-navigation/native'; import { useNavigation, useRoute, RouteProp, CommonActions } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { SafeAreaView } from 'react-native-safe-area-context';
import { RootStackParamList } from '../navigation/types'; import { RootStackParamList } from '../navigation/types';
import { import {
StepType, StepType,
@ -15,26 +16,26 @@ import {
KilnType, KilnType,
KilnPosition, KilnPosition,
GlazeLayer, GlazeLayer,
GlazePosition,
ApplicationMethod ApplicationMethod
} from '../types'; } from '../types';
import { createStep, getStep, updateStep, deleteStep, getSettings } from '../lib/db/repositories'; import { createStep, getStep, updateStep, deleteStep, getSettings } from '../lib/db/repositories';
import { getConeTemperature, getBisqueCones, getGlazeFiringCones } from '../lib/utils'; import { getConeTemperature } from '../lib/utils';
import { Button, Input, Card, Picker, WeightInput, ButtonGrid, DimensionsInput, GlazeLayerCard } from '../components'; import { Button, Input, Card, WeightInput, ButtonGrid, DimensionsInput, GlazeLayerCard } from '../components';
import { colors, spacing, typography, borderRadius } from '../lib/theme'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { colors, spacing, typography, borderRadius, stepTypeIcons, stepTypeColors, shadows } from '../lib/theme';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
type NavigationProp = NativeStackNavigationProp<RootStackParamList, 'StepEditor'>; type NavigationProp = NativeStackNavigationProp<RootStackParamList, 'StepEditor'>;
type RouteProps = RouteProp<RootStackParamList, 'StepEditor'>; type RouteProps = RouteProp<RootStackParamList, 'StepEditor'>;
const STEP_TYPES: { value: StepType; label: string }[] = [ const STEP_TYPES: { value: StepType; label: string }[] = [
{ value: 'forming', label: '🏺 Forming' }, { value: 'forming', label: 'Forming' },
{ value: 'drying', label: '☀️ Drying' }, { value: 'trimming', label: 'Trimming' },
{ value: 'bisque_firing', label: '🔥 Bisque Firing' }, { value: 'drying', label: 'Drying' },
{ value: 'trimming', label: '🔧 Trimming' }, { value: 'bisque_firing', label: 'Bisque Firing' },
{ value: 'glazing', label: '🎨 Glazing' }, { value: 'glazing', label: 'Glazing' },
{ value: 'glaze_firing', label: 'Glaze Firing' }, { value: 'glaze_firing', label: 'Glaze Firing' },
{ value: 'misc', label: '📝 Misc' }, { value: 'misc', label: 'Misc' },
]; ];
export const StepEditorScreen: React.FC = () => { export const StepEditorScreen: React.FC = () => {
@ -44,14 +45,15 @@ export const StepEditorScreen: React.FC = () => {
const { projectId, stepId } = route.params; const { projectId, stepId } = route.params;
const isEditing = !!stepId; const isEditing = !!stepId;
// Create unique editor key for this screen instance
const editorKey = React.useRef(route.params._editorKey || `editor-${Date.now()}`).current;
// Refs for scrolling to glaze layers section // Refs for scrolling to glaze layers section
const scrollViewRef = useRef<KeyboardAwareScrollView>(null); const scrollViewRef = useRef<KeyboardAwareScrollView>(null);
const glazeLayersSectionRef = useRef<View>(null); const glazeLayersSectionRef = useRef<View>(null);
const [stepType, setStepType] = useState<StepType>('forming'); React.useLayoutEffect(() => {
navigation.setOptions({ headerShown: false });
}, [navigation]);
const [stepType, setStepType] = useState<StepType>(route.params.initialStepType || 'forming');
const [notes, setNotes] = useState(''); const [notes, setNotes] = useState('');
const [photos, setPhotos] = useState<string[]>([]); const [photos, setPhotos] = useState<string[]>([]);
@ -66,7 +68,7 @@ export const StepEditorScreen: React.FC = () => {
const [productionMethod, setProductionMethod] = useState<ProductionMethod>('wheel'); const [productionMethod, setProductionMethod] = useState<ProductionMethod>('wheel');
const [dimensions, setDimensions] = useState<Dimensions | undefined>(undefined); const [dimensions, setDimensions] = useState<Dimensions | undefined>(undefined);
// Firing fields (existing + new) // Firing fields
const [cone, setCone] = useState(''); const [cone, setCone] = useState('');
const [temperature, setTemperature] = useState(''); const [temperature, setTemperature] = useState('');
const [duration, setDuration] = useState(''); const [duration, setDuration] = useState('');
@ -77,11 +79,9 @@ export const StepEditorScreen: React.FC = () => {
const [kilnPositionNotes, setKilnPositionNotes] = useState(''); const [kilnPositionNotes, setKilnPositionNotes] = useState('');
const [firingSchedule, setFiringSchedule] = useState(''); const [firingSchedule, setFiringSchedule] = useState('');
// Glazing fields (multi-layer) // Glazing fields
const [glazeLayers, setGlazeLayers] = useState<GlazeLayer[]>([]); const [glazeLayers, setGlazeLayers] = useState<GlazeLayer[]>([]);
const [customGlazeName, setCustomGlazeName] = useState(''); const [customGlazeName, setCustomGlazeName] = useState('');
// Legacy glazing fields (for backwards compatibility)
const [coats, setCoats] = useState('2'); const [coats, setCoats] = useState('2');
const [applicationMethod, setApplicationMethod] = useState<ApplicationMethod>('brush'); const [applicationMethod, setApplicationMethod] = useState<ApplicationMethod>('brush');
const [selectedGlazeIds, setSelectedGlazeIds] = useState<string[]>([]); const [selectedGlazeIds, setSelectedGlazeIds] = useState<string[]>([]);
@ -90,9 +90,6 @@ export const StepEditorScreen: React.FC = () => {
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [loading, setLoading] = useState(isEditing); const [loading, setLoading] = useState(isEditing);
// Track if we're adding glazes from catalog (new flow)
const [isAddingFromCatalog, setIsAddingFromCatalog] = useState(false);
// Load user's preferences // Load user's preferences
useEffect(() => { useEffect(() => {
loadUserPreferences(); loadUserPreferences();
@ -117,127 +114,15 @@ export const StepEditorScreen: React.FC = () => {
}, [stepId]); }, [stepId]);
// Callback ref for receiving glaze selection from GlazePicker // Callback ref for receiving glaze selection from GlazePicker
// This is a clean, direct approach without route params or useEffects
const glazeSelectionCallbackRef = useRef<((layers: GlazeLayer[]) => void) | null>(null); const glazeSelectionCallbackRef = useRef<((layers: GlazeLayer[]) => void) | null>(null);
// TEMPORARILY DISABLED - Auto-save conflicts with new glaze layer flow // Auto-save whenever any field changes
// This useEffect was causing immediate navigation.reset() back to ProjectDetail
// when adding glazes from catalog, preventing users from editing glaze layers.
// TODO: Re-enable with proper flow detection once new glaze flow is stable.
/*
// Also watch for route param changes directly and auto-save
useEffect(() => { useEffect(() => {
// Skip auto-save if we're in the new add_layer flow
// Check both the flag AND if we just processed newGlazeLayers
if (isAddingFromCatalog || (route.params && 'newGlazeLayers' in route.params)) {
console.log('Skipping auto-save - user is adding from catalog or new layers present');
return;
}
let hasChanges = false;
if (route.params && 'selectedGlazeIds' in route.params && route.params.selectedGlazeIds) {
console.log('Route params changed - selected glaze IDs:', route.params.selectedGlazeIds);
setSelectedGlazeIds(route.params.selectedGlazeIds);
// IMPORTANT: Automatically set stepType to 'glazing' when glazes are selected
if (stepType !== 'glazing') {
console.log('Auto-setting stepType to glazing');
setStepType('glazing');
}
hasChanges = true;
}
if (route.params && 'mixNotes' in route.params && route.params.mixNotes) {
console.log('Route params changed - mix notes:', route.params.mixNotes);
setMixNotes(route.params.mixNotes);
hasChanges = true;
}
// Auto-save after state updates
if (hasChanges) {
console.log('Auto-save triggered - stepType:', stepType, 'isEditing:', isEditing, 'stepId:', stepId);
// Use setTimeout to wait for state updates to complete
setTimeout(async () => {
try {
// Always use 'glazing' type when we have glaze IDs
const finalStepType = route.params?.selectedGlazeIds ? 'glazing' : stepType;
const stepData: any = {
projectId,
type: finalStepType,
notesMarkdown: notes,
photoUris: photos,
};
// Add glazing data if we have glaze selections
if (route.params?.selectedGlazeIds) {
stepData.glazing = {
glazeIds: route.params.selectedGlazeIds,
coats: coats ? parseInt(coats) : undefined,
application: applicationMethod,
mixNotes: route.params.mixNotes || undefined,
};
console.log('Glazing data prepared:', stepData.glazing);
}
if (isEditing && stepId) {
console.log('Updating existing step:', stepId);
await updateStep(stepId, stepData);
console.log('✅ Auto-saved step with glazes');
// Reset navigation to ProjectDetail, keeping MainTabs in stack
console.log('Resetting stack to MainTabs → ProjectDetail');
navigation.dispatch(
CommonActions.reset({
index: 1, // ProjectDetail is the active screen
routes: [
{ name: 'MainTabs' }, // Keep MainTabs in stack for back button
{ name: 'ProjectDetail', params: { projectId } }, // ProjectDetail is active
],
})
);
} else if (!isEditing) {
// If creating a new step, create it first
console.log('Creating new step with type:', finalStepType);
const newStep = await createStep(stepData);
if (newStep) {
console.log('✅ Auto-created step with glazes, new stepId:', newStep.id);
// Reset navigation to ProjectDetail, keeping MainTabs in stack
console.log('Resetting stack to MainTabs → ProjectDetail');
navigation.dispatch(
CommonActions.reset({
index: 1, // ProjectDetail is the active screen
routes: [
{ name: 'MainTabs' }, // Keep MainTabs in stack for back button
{ name: 'ProjectDetail', params: { projectId } }, // ProjectDetail is active
],
})
);
}
}
} catch (error) {
console.error('❌ Auto-save failed:', error);
}
}, 100);
}
}, [route.params?.selectedGlazeIds, route.params?.mixNotes]);
*/
// Auto-save whenever any field changes (but only for existing steps)
useEffect(() => {
// Don't auto-save on initial load or if we're still loading
if (loading) return; if (loading) return;
// Only auto-save for existing steps that have been edited
if (isEditing && stepId) { if (isEditing && stepId) {
console.log('Field changed - triggering auto-save');
// Debounce the save by 1 second to avoid saving on every keystroke
const timeoutId = setTimeout(async () => { const timeoutId = setTimeout(async () => {
try { try {
// Prepare data...
const stepData: any = { const stepData: any = {
projectId, projectId,
type: stepType, type: stepType,
@ -279,39 +164,15 @@ export const StepEditorScreen: React.FC = () => {
} catch (error) { } catch (error) {
console.error('Auto-save error:', error); console.error('Auto-save error:', error);
} }
}, 1000); // Wait 1 second after last change }, 1000);
return () => clearTimeout(timeoutId); return () => clearTimeout(timeoutId);
} }
}, [ }, [
stepType, stepType, notes, photos,
notes, clayBody, clayWeight, productionMethod, dimensions,
photos, cone, temperature, duration, kilnNotes, kilnType, kilnCustomName, kilnPosition, kilnPositionNotes, firingSchedule,
// Forming fields glazeLayers, coats, applicationMethod, selectedGlazeIds, mixNotes,
clayBody, isEditing, stepId, loading, projectId, tempUnit
clayWeight,
productionMethod,
dimensions,
// Firing fields
cone,
temperature,
duration,
kilnNotes,
kilnType,
kilnCustomName,
kilnPosition,
kilnPositionNotes,
firingSchedule,
// Glazing fields
glazeLayers,
coats,
applicationMethod,
selectedGlazeIds,
mixNotes,
// Meta
isEditing,
stepId,
loading,
]); ]);
const loadStep = async () => { const loadStep = async () => {
@ -322,7 +183,6 @@ export const StepEditorScreen: React.FC = () => {
navigation.goBack(); navigation.goBack();
return; return;
} }
setStepType(step.type); setStepType(step.type);
setNotes(step.notesMarkdown || ''); setNotes(step.notesMarkdown || '');
setPhotos(step.photoUris || []); setPhotos(step.photoUris || []);
@ -351,11 +211,9 @@ export const StepEditorScreen: React.FC = () => {
} else if (step.type === 'glazing') { } else if (step.type === 'glazing') {
const glazing = (step as any).glazing; const glazing = (step as any).glazing;
if (glazing) { if (glazing) {
// New multi-layer format
if (glazing.glazeLayers && glazing.glazeLayers.length > 0) { if (glazing.glazeLayers && glazing.glazeLayers.length > 0) {
setGlazeLayers(glazing.glazeLayers); setGlazeLayers(glazing.glazeLayers);
} else { } else {
// Legacy format - keep for compatibility
setCoats(glazing.coats?.toString() || '2'); setCoats(glazing.coats?.toString() || '2');
setApplicationMethod(glazing.application || 'brush'); setApplicationMethod(glazing.application || 'brush');
setSelectedGlazeIds(glazing.glazeIds || []); setSelectedGlazeIds(glazing.glazeIds || []);
@ -373,7 +231,7 @@ export const StepEditorScreen: React.FC = () => {
const handleConeChange = (value: string) => { const handleConeChange = (value: string) => {
setCone(value); setCone(value);
const temp = getConeTemperature(value, tempUnit); // Pass user's preferred unit const temp = getConeTemperature(value, tempUnit);
if (temp) { if (temp) {
setTemperature(temp.value.toString()); setTemperature(temp.value.toString());
} }
@ -385,13 +243,11 @@ export const StepEditorScreen: React.FC = () => {
Alert.alert('Permission needed', 'We need camera permissions to take photos'); Alert.alert('Permission needed', 'We need camera permissions to take photos');
return; return;
} }
const result = await ImagePicker.launchCameraAsync({ const result = await ImagePicker.launchCameraAsync({
allowsEditing: true, allowsEditing: true,
aspect: [4, 3], aspect: [4, 3],
quality: 0.8, quality: 0.8,
}); });
if (!result.canceled && result.assets[0]) { if (!result.canceled && result.assets[0]) {
setPhotos([...photos, result.assets[0].uri]); setPhotos([...photos, result.assets[0].uri]);
} }
@ -403,29 +259,23 @@ export const StepEditorScreen: React.FC = () => {
Alert.alert('Permission needed', 'We need camera roll permissions to add photos'); Alert.alert('Permission needed', 'We need camera roll permissions to add photos');
return; return;
} }
const result = await ImagePicker.launchImageLibraryAsync({ const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: 'images', mediaTypes: 'images',
allowsEditing: true, allowsEditing: true,
aspect: [4, 3], aspect: [4, 3],
quality: 0.8, quality: 0.8,
}); });
if (!result.canceled && result.assets[0]) { if (!result.canceled && result.assets[0]) {
setPhotos([...photos, result.assets[0].uri]); setPhotos([...photos, result.assets[0].uri]);
} }
}; };
const handleAddPhoto = () => { const handleAddPhoto = () => {
Alert.alert( Alert.alert('Add Photo', 'Choose a source', [
'Add Photo',
'Choose a source',
[
{ text: 'Camera', onPress: takePhoto }, { text: 'Camera', onPress: takePhoto },
{ text: 'Gallery', onPress: pickImage }, { text: 'Gallery', onPress: pickImage },
{ text: 'Cancel', style: 'cancel' }, { text: 'Cancel', style: 'cancel' },
] ]);
);
}; };
const removePhoto = (index: number) => { const removePhoto = (index: number) => {
@ -433,19 +283,11 @@ export const StepEditorScreen: React.FC = () => {
}; };
const handleAddFromCatalog = () => { const handleAddFromCatalog = () => {
console.log('Add from Catalog clicked - setting up callback and navigating to GlazePicker');
// Set up callback BEFORE navigating - this will be called directly by GlazePicker
glazeSelectionCallbackRef.current = (newLayers: GlazeLayer[]) => { glazeSelectionCallbackRef.current = (newLayers: GlazeLayer[]) => {
console.log('Callback received layers:', newLayers);
setGlazeLayers(prevLayers => [...prevLayers, ...newLayers]); setGlazeLayers(prevLayers => [...prevLayers, ...newLayers]);
if (stepType !== 'glazing') { if (stepType !== 'glazing') {
console.log('Setting stepType to glazing');
setStepType('glazing'); setStepType('glazing');
} }
// Scroll to glaze layers section after state update
setTimeout(() => { setTimeout(() => {
if (glazeLayersSectionRef.current && scrollViewRef.current) { if (glazeLayersSectionRef.current && scrollViewRef.current) {
glazeLayersSectionRef.current.measure((x, y, width, height, pageX, pageY) => { glazeLayersSectionRef.current.measure((x, y, width, height, pageX, pageY) => {
@ -456,7 +298,6 @@ export const StepEditorScreen: React.FC = () => {
} }
}, 200); }, 200);
}; };
navigation.navigate('GlazePicker', { navigation.navigate('GlazePicker', {
projectId, projectId,
stepId, stepId,
@ -497,7 +338,6 @@ export const StepEditorScreen: React.FC = () => {
} else if (stepType === 'glazing') { } else if (stepType === 'glazing') {
stepData.glazing = { stepData.glazing = {
glazeLayers: glazeLayers.length > 0 ? glazeLayers : undefined, glazeLayers: glazeLayers.length > 0 ? glazeLayers : undefined,
// Legacy fields for backwards compatibility
glazeIds: selectedGlazeIds.length > 0 ? selectedGlazeIds : undefined, glazeIds: selectedGlazeIds.length > 0 ? selectedGlazeIds : undefined,
coats: coats ? parseInt(coats) : undefined, coats: coats ? parseInt(coats) : undefined,
application: applicationMethod, application: applicationMethod,
@ -507,20 +347,16 @@ export const StepEditorScreen: React.FC = () => {
if (isEditing) { if (isEditing) {
await updateStep(stepId!, stepData); await updateStep(stepId!, stepData);
console.log('✅ Step updated successfully');
} else { } else {
const newStep = await createStep(stepData); await createStep(stepData);
console.log('✅ Step created successfully, id:', newStep?.id);
} }
// Reset navigation to ProjectDetail, keeping MainTabs in stack
console.log('Resetting stack to MainTabs → ProjectDetail after manual save');
navigation.dispatch( navigation.dispatch(
CommonActions.reset({ CommonActions.reset({
index: 1, // ProjectDetail is the active screen index: 1,
routes: [ routes: [
{ name: 'MainTabs' }, // Keep MainTabs in stack for back button { name: 'MainTabs' },
{ name: 'ProjectDetail', params: { projectId } }, // ProjectDetail is active { name: 'ProjectDetail', params: { projectId } },
], ],
}) })
); );
@ -533,10 +369,7 @@ export const StepEditorScreen: React.FC = () => {
}; };
const handleDelete = () => { const handleDelete = () => {
Alert.alert( Alert.alert('Delete Step', 'Are you sure you want to delete this step?', [
'Delete Step',
'Are you sure you want to delete this step?',
[
{ text: 'Cancel', style: 'cancel' }, { text: 'Cancel', style: 'cancel' },
{ {
text: 'Delete', text: 'Delete',
@ -544,16 +377,17 @@ export const StepEditorScreen: React.FC = () => {
onPress: async () => { onPress: async () => {
try { try {
await deleteStep(stepId!); await deleteStep(stepId!);
Alert.alert('Success', 'Step deleted');
navigation.goBack(); navigation.goBack();
} catch (error) { } catch (error) {
console.error('Failed to delete step:', error);
Alert.alert('Error', 'Failed to delete step'); Alert.alert('Error', 'Failed to delete step');
} }
}, },
}, },
] ]);
); };
const handleCancel = () => {
navigation.goBack();
}; };
const renderFormingFields = () => { const renderFormingFields = () => {
@ -572,22 +406,19 @@ export const StepEditorScreen: React.FC = () => {
onChangeText={setClayBody} onChangeText={setClayBody}
placeholder="e.g., B-Mix, Porcelain" placeholder="e.g., B-Mix, Porcelain"
/> />
<WeightInput <WeightInput
label="Clay Weight" label="Clay Weight"
value={clayWeight} value={clayWeight}
onChange={setClayWeight} onChange={setClayWeight}
userPreferredUnit={weightUnit} userPreferredUnit={weightUnit}
/> />
<Text style={styles.sectionLabel}>Production Method</Text>
<Text style={styles.label}>Production Method</Text>
<ButtonGrid <ButtonGrid
options={productionMethods} options={productionMethods}
selectedValue={productionMethod} selectedValue={productionMethod}
onSelect={(value) => setProductionMethod(value as ProductionMethod)} onSelect={(value) => setProductionMethod(value as ProductionMethod)}
columns={2} columns={2}
/> />
<DimensionsInput <DimensionsInput
label="Dimensions" label="Dimensions"
value={dimensions} value={dimensions}
@ -605,7 +436,6 @@ export const StepEditorScreen: React.FC = () => {
{ value: 'raku', label: 'Raku' }, { value: 'raku', label: 'Raku' },
{ value: 'other', label: 'Other' }, { value: 'other', label: 'Other' },
]; ];
const kilnPositions = [ const kilnPositions = [
{ value: 'top', label: 'Top' }, { value: 'top', label: 'Top' },
{ value: 'middle', label: 'Middle' }, { value: 'middle', label: 'Middle' },
@ -635,15 +465,13 @@ export const StepEditorScreen: React.FC = () => {
placeholder="Total firing time" placeholder="Total firing time"
keyboardType="numeric" keyboardType="numeric"
/> />
<Text style={styles.sectionLabel}>Kiln Type</Text>
<Text style={styles.label}>Kiln Type</Text>
<ButtonGrid <ButtonGrid
options={kilnTypes} options={kilnTypes}
selectedValue={kilnType} selectedValue={kilnType}
onSelect={(value) => setKilnType(value as KilnType)} onSelect={(value) => setKilnType(value as KilnType)}
columns={2} columns={2}
/> />
{kilnType === 'other' && ( {kilnType === 'other' && (
<Input <Input
label="Custom Kiln Name" label="Custom Kiln Name"
@ -652,15 +480,13 @@ export const StepEditorScreen: React.FC = () => {
placeholder="Enter kiln name" placeholder="Enter kiln name"
/> />
)} )}
<Text style={styles.sectionLabel}>Position in Kiln</Text>
<Text style={styles.label}>Position in Kiln</Text>
<ButtonGrid <ButtonGrid
options={kilnPositions} options={kilnPositions}
selectedValue={kilnPosition} selectedValue={kilnPosition}
onSelect={(value) => setKilnPosition(value as KilnPosition)} onSelect={(value) => setKilnPosition(value as KilnPosition)}
columns={2} columns={2}
/> />
{kilnPosition === 'other' && ( {kilnPosition === 'other' && (
<Input <Input
label="Position Notes" label="Position Notes"
@ -669,7 +495,6 @@ export const StepEditorScreen: React.FC = () => {
placeholder="Describe position" placeholder="Describe position"
/> />
)} )}
<Input <Input
label="Firing Schedule" label="Firing Schedule"
value={firingSchedule} value={firingSchedule}
@ -678,7 +503,6 @@ export const StepEditorScreen: React.FC = () => {
multiline multiline
numberOfLines={4} numberOfLines={4}
/> />
<Input <Input
label="Kiln Notes" label="Kiln Notes"
value={kilnNotes} value={kilnNotes}
@ -693,7 +517,6 @@ export const StepEditorScreen: React.FC = () => {
const renderGlazingFields = () => { const renderGlazingFields = () => {
const handleAddCustomGlaze = () => { const handleAddCustomGlaze = () => {
if (!customGlazeName.trim()) return; if (!customGlazeName.trim()) return;
const newLayer: GlazeLayer = { const newLayer: GlazeLayer = {
customGlazeName: customGlazeName.trim(), customGlazeName: customGlazeName.trim(),
application: 'brush', application: 'brush',
@ -701,7 +524,6 @@ export const StepEditorScreen: React.FC = () => {
coats: 2, coats: 2,
notes: '', notes: '',
}; };
setGlazeLayers([...glazeLayers, newLayer]); setGlazeLayers([...glazeLayers, newLayer]);
setCustomGlazeName(''); setCustomGlazeName('');
}; };
@ -719,8 +541,7 @@ export const StepEditorScreen: React.FC = () => {
return ( return (
<> <>
<View ref={glazeLayersSectionRef}> <View ref={glazeLayersSectionRef}>
<Text style={styles.label}>Glaze Layers</Text> <Text style={styles.sectionLabel}>Glaze Layers</Text>
{glazeLayers.map((layer, index) => ( {glazeLayers.map((layer, index) => (
<GlazeLayerCard <GlazeLayerCard
key={index} key={index}
@ -733,7 +554,7 @@ export const StepEditorScreen: React.FC = () => {
</View> </View>
<View style={styles.addGlazeContainer}> <View style={styles.addGlazeContainer}>
<Text style={styles.label}>Add Custom Glaze</Text> <Text style={styles.sectionLabel}>Add Custom Glaze</Text>
<View style={styles.customGlazeRow}> <View style={styles.customGlazeRow}>
<TextInput <TextInput
style={styles.customGlazeInput} style={styles.customGlazeInput}
@ -776,7 +597,27 @@ export const StepEditorScreen: React.FC = () => {
); );
}; };
const stepTypeOptions = STEP_TYPES.map(type => ({
...type,
icon: (
<MaterialCommunityIcons
name={stepTypeIcons[type.value] as any}
size={24}
color={stepType === type.value ? colors.buttonText : stepTypeColors[type.value]}
/>
)
}));
return ( return (
<SafeAreaView style={styles.safeArea} edges={['top']}>
<View style={styles.header}>
<TouchableOpacity style={styles.backButton} onPress={handleCancel}>
<MaterialCommunityIcons name="chevron-left" size={24} color={colors.text} />
<Text style={styles.backButtonText}>Back</Text>
</TouchableOpacity>
<Text style={styles.headerTitle}>{isEditing ? 'Edit Step' : 'Add Step'}</Text>
</View>
<KeyboardAwareScrollView <KeyboardAwareScrollView
ref={scrollViewRef} ref={scrollViewRef}
style={styles.container} style={styles.container}
@ -787,25 +628,19 @@ export const StepEditorScreen: React.FC = () => {
keyboardShouldPersistTaps="handled" keyboardShouldPersistTaps="handled"
> >
<Card> <Card>
<Text style={styles.label}>Step Type</Text> <Text style={styles.sectionLabel}>Step Type</Text>
<View style={styles.typeGrid}> <ButtonGrid
{STEP_TYPES.map((type) => ( options={stepTypeOptions}
<Button selectedValue={stepType}
key={type.value} onSelect={(value) => setStepType(value as StepType)}
title={type.label} columns={2}
onPress={() => setStepType(type.value)}
variant={stepType === type.value ? 'primary' : 'outline'}
size="sm"
style={styles.typeButton}
/> />
))}
</View>
{stepType === 'forming' && renderFormingFields()} {stepType === 'forming' && renderFormingFields()}
{(stepType === 'bisque_firing' || stepType === 'glaze_firing') && renderFiringFields()} {(stepType === 'bisque_firing' || stepType === 'glaze_firing') && renderFiringFields()}
{stepType === 'glazing' && renderGlazingFields()} {stepType === 'glazing' && renderGlazingFields()}
<Text style={styles.label}>Photos</Text> <Text style={styles.sectionLabel}>Photos</Text>
<View style={styles.photosContainer}> <View style={styles.photosContainer}>
{photos.map((uri, index) => ( {photos.map((uri, index) => (
<View key={index} style={styles.photoWrapper}> <View key={index} style={styles.photoWrapper}>
@ -856,10 +691,51 @@ export const StepEditorScreen: React.FC = () => {
)} )}
</Card> </Card>
</KeyboardAwareScrollView> </KeyboardAwareScrollView>
</SafeAreaView>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: colors.background,
},
header: {
flexDirection: 'row',
alignItems: 'center', // content centered horizontally via absolute positioning
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
backgroundColor: colors.background,
borderBottomWidth: 1,
borderBottomColor: colors.border,
position: 'relative', // Context for absolute children
},
headerTitle: {
position: 'absolute',
left: 0,
right: 0,
textAlign: 'center',
fontSize: 24,
fontWeight: '800',
color: colors.text,
},
backButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.card,
borderRadius: borderRadius.full,
paddingVertical: spacing.xs,
paddingHorizontal: spacing.sm,
paddingRight: spacing.md,
...shadows.sm,
zIndex: 10,
},
backButtonText: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.medium,
color: colors.text,
marginLeft: -4,
},
container: { container: {
flex: 1, flex: 1,
backgroundColor: colors.backgroundSecondary, backgroundColor: colors.backgroundSecondary,
@ -868,22 +744,14 @@ const styles = StyleSheet.create({
padding: spacing.md, padding: spacing.md,
paddingBottom: spacing.xxl, paddingBottom: spacing.xxl,
}, },
label: { sectionLabel: {
fontSize: typography.fontSize.sm, fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold, fontWeight: typography.fontWeight.bold,
color: colors.text, color: colors.text,
marginBottom: spacing.sm, marginBottom: spacing.sm,
letterSpacing: 0.5, letterSpacing: 0.5,
textTransform: 'uppercase', textTransform: 'uppercase',
}, marginTop: spacing.md,
typeGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.sm,
marginBottom: spacing.lg,
},
typeButton: {
minWidth: '30%',
}, },
note: { note: {
fontSize: typography.fontSize.sm, fontSize: typography.fontSize.sm,
@ -891,6 +759,9 @@ const styles = StyleSheet.create({
fontStyle: 'italic', fontStyle: 'italic',
marginTop: spacing.sm, marginTop: spacing.sm,
}, },
typeGrid: {
marginBottom: 120, // Much larger separation as requested
},
photosContainer: { photosContainer: {
flexDirection: 'row', flexDirection: 'row',
flexWrap: 'wrap', flexWrap: 'wrap',
@ -951,44 +822,6 @@ const styles = StyleSheet.create({
deleteButtonInSticky: { deleteButtonInSticky: {
marginTop: spacing.md, marginTop: spacing.md,
}, },
catalogButtonInSticky: {
marginBottom: spacing.md,
},
applicationGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.sm,
marginBottom: spacing.lg,
},
applicationButton: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderRadius: borderRadius.md,
borderWidth: 2,
borderColor: colors.border,
backgroundColor: colors.backgroundSecondary,
gap: spacing.xs,
},
applicationButtonActive: {
borderColor: colors.primary,
backgroundColor: colors.primaryLight,
},
applicationIcon: {
fontSize: 18,
},
applicationText: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
},
applicationTextActive: {
color: colors.text,
},
glazePickerButton: {
marginBottom: spacing.sm,
},
addGlazeContainer: { addGlazeContainer: {
marginBottom: spacing.md, marginBottom: spacing.md,
}, },
@ -1018,11 +851,4 @@ const styles = StyleSheet.create({
marginTop: spacing.xs, marginTop: spacing.xs,
marginBottom: spacing.md, marginBottom: spacing.md,
}, },
stickyButtonContainer: {
padding: spacing.md,
paddingBottom: spacing.xl,
backgroundColor: colors.backgroundSecondary,
borderTopWidth: 2,
borderTopColor: colors.border,
},
}); });

8
verbesserungen.md Normal file
View File

@ -0,0 +1,8 @@
farben ändern
suggested next step -> wenn mn auf add step klickt -> der suggested step
auf suggested steps klicken im project overview
Glazing: unter layer kein scroll karousell ->button auswahl
suggested step: immer der nächste step, egal ob man was übersprungen hat, z.B. nach glazing kann man nicht mehr trimmen
icons