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

View File

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

View File

@ -8,7 +8,8 @@ export interface EmptyStateProps {
title: string;
description?: string;
actionLabel?: string;
onAction?: () => void;
onAction?: () => void; // Restored
buttonStyle?: import('react-native').ViewStyle;
}
export const EmptyState: React.FC<EmptyStateProps> = ({
@ -17,6 +18,7 @@ export const EmptyState: React.FC<EmptyStateProps> = ({
description,
actionLabel,
onAction,
buttonStyle,
}) => {
return (
<View style={styles.container}>
@ -28,7 +30,8 @@ export const EmptyState: React.FC<EmptyStateProps> = ({
title={actionLabel}
onPress={onAction}
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 File

@ -3,6 +3,7 @@ import { View, Text, StyleSheet, TextInput } from 'react-native';
import { Card } from './Card';
import { Button } from './Button';
import { Picker } from './Picker';
import { ButtonGrid } from './ButtonGrid'; // NEW
import { colors, spacing, typography, borderRadius } from '../lib/theme';
import { GlazeLayer, ApplicationMethod, GlazePosition } from '../types';
import { getGlaze } from '../lib/db/repositories';
@ -76,9 +77,8 @@ export const GlazeLayerCard: React.FC<GlazeLayerCardProps> = ({
<Text style={styles.glazeName}>{glazeName}</Text>
<Picker
label="Application Method"
value={layer.application}
<Text style={styles.label}>Application Method</Text>
<ButtonGrid
options={[
{ value: 'brush', label: 'Brushing' },
{ value: 'dip', label: 'Dipping' },
@ -86,12 +86,13 @@ export const GlazeLayerCard: React.FC<GlazeLayerCardProps> = ({
{ value: 'spray', label: 'Spraying' },
{ value: 'other', label: 'Other' },
]}
onValueChange={handleApplicationChange}
selectedValue={layer.application}
onSelect={handleApplicationChange}
columns={2}
/>
<Picker
label="Position on Piece"
value={layer.position}
<Text style={styles.label}>Position on Piece</Text>
<ButtonGrid
options={[
{ value: 'entire', label: 'Entire Piece' },
{ value: 'top', label: 'Top' },
@ -101,7 +102,9 @@ export const GlazeLayerCard: React.FC<GlazeLayerCardProps> = ({
{ value: 'outside', label: 'Outside' },
{ value: 'other', label: 'Other' },
]}
onValueChange={handlePositionChange}
selectedValue={layer.position}
onSelect={handlePositionChange}
columns={2}
/>
<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));
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);
let isCompleted = false;
if (onboardingData) {
const completedUsers = JSON.parse(onboardingData);
setHasCompletedOnboarding(completedUsers[currentUser.id] === true);
} else {
setHasCompletedOnboarding(false);
isCompleted = completedUsers[currentUser.id] === true;
}
// Update states together
setHasCompletedOnboarding(isCompleted);
setUser(currentUser);
} catch (error) {
console.error('Sign in failed:', error);
throw error;

View File

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

View File

@ -5,9 +5,9 @@ import { Step, StepType } from '../../types';
*/
const STEP_ORDER: StepType[] = [
'forming',
'trimming',
'drying',
'bisque_firing',
'trimming',
'glazing',
'glaze_firing',
'misc',
@ -44,17 +44,30 @@ export function sortStepsByLogicalOrder(steps: Step[]): Step[] {
* Returns null if all typical steps are completed
*/
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));
// Find first step type not yet completed
for (const stepType of STEP_ORDER) {
if (stepType === 'misc') continue; // Skip misc, it's optional
if (!completedTypes.has(stepType)) {
return stepType;
for (const stepType of completedTypes) {
const index = getStepOrderIndex(stepType as StepType);
if (index > highestIndex && index < 999) { // 999 is invalid/misc
highestIndex = index;
}
}
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({
tabBarContainer: {
flexDirection: 'row',
backgroundColor: colors.background,
backgroundColor: '#FFFFFF',
height: 65,
position: 'absolute',
bottom: 20,
left: 10,
right: 10,
borderRadius: 25,
shadowColor: colors.text,
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 8,
// Strong floating shadow
shadowColor: '#000000',
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.15,
shadowRadius: 16,
elevation: 12,
},
tabItem: {
flex: 1,
@ -156,18 +157,31 @@ const styles = StyleSheet.create({
paddingVertical: 8,
},
tabItemActive: {
backgroundColor: colors.primaryLight,
borderWidth: 2,
borderColor: colors.primary,
backgroundColor: '#DCDCDC', // Darker inner gray
borderColor: '#FFFFFF', // Light outer border
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: {
borderRadius: 25,
borderTopLeftRadius: 20,
borderBottomLeftRadius: 20,
},
tabItemActiveLast: {
borderRadius: 25,
borderTopRightRadius: 20,
borderBottomRightRadius: 20,
},
tabItemActiveMiddle: {
borderRadius: 25,
borderRadius: 18,
},
imageIconContainer: {
width: 40,
@ -245,14 +259,16 @@ export function AppNavigator() {
options={{ headerShown: false }}
/>
</>
) : (
// App screens - logged in (initial route determined by hasCompletedOnboarding)
<>
) : !hasCompletedOnboarding ? (
// Onboarding flow - explicit separation ensures it's not skipped
<RootStack.Screen
name="Onboarding"
component={OnboardingScreen}
options={{ headerShown: false }}
/>
) : (
// App screens - logged in AND onboarding completed
<>
<RootStack.Screen
name="MainTabs"
component={MainTabs}

View File

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

View File

@ -15,11 +15,25 @@ import { RootStackParamList } from '../navigation/types';
import { Project, Step } from '../types';
import { getAllProjects, getStepsByProject } from '../lib/db/repositories';
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';
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>;
export const HomeScreen: React.FC = () => {
@ -83,7 +97,7 @@ export const HomeScreen: React.FC = () => {
// Calculate stats
const totalProjects = projects.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)
const recentProjects = projects
@ -115,39 +129,39 @@ export const HomeScreen: React.FC = () => {
}
return (
<SafeAreaView style={styles.container} edges={['top']}>
<SafeAreaView style={styles.container} edges={[]}>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
contentContainerStyle={styles.scrollContentNoPadding}
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 */}
<View style={styles.topBar}>
<View style={styles.greetingContainer}>
<Text style={styles.greetingText}>Welcome Back,</Text>
<Text style={styles.nameText}>{user?.name || 'Potter'}</Text>
</View>
<TouchableOpacity
style={styles.settingsButton}
onPress={() => navigation.navigate('MainTabs', { screen: 'Settings' })}
>
<Image
source={settingsIcon}
style={styles.settingsIconImage}
resizeMode="contain"
/>
</TouchableOpacity>
</View>
{/* Stats Section */}
{/* Stats */}
<View style={styles.statsContainer}>
<View style={styles.statCard}>
<Text style={styles.statLabel}>Total Projects</Text>
<View style={styles.statsRow}>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{totalProjects}</Text>
<Text style={styles.statLabel}>Projects</Text>
</View>
<View style={styles.statCard}>
<Text style={styles.statLabel}>In Progress</Text>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{inProgressProjects}</Text>
<Text style={styles.statLabel}>In Progress</Text>
</View>
</View>
</View>
</View>
</View>
@ -159,12 +173,6 @@ export const HomeScreen: React.FC = () => {
>
<Text style={styles.primaryButtonText}>New Pottery Project</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.secondaryButton}
onPress={handleBrowseTips}
>
<Text style={styles.secondaryButtonText}>Browse Pottery Tips</Text>
</TouchableOpacity>
</View>
{/* Section Header */}
@ -248,12 +256,16 @@ const styles = StyleSheet.create({
paddingHorizontal: spacing.lg,
paddingBottom: 100,
},
scrollContentNoPadding: {
paddingBottom: 100,
},
topBar: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
paddingVertical: spacing.lg,
marginBottom: spacing.lg,
paddingTop: spacing.xl * 3,
paddingHorizontal: spacing.lg,
marginBottom: spacing.md,
gap: spacing.lg,
},
greetingContainer: {
@ -279,40 +291,70 @@ const styles = StyleSheet.create({
height: 32,
opacity: 0.8,
},
statsContainer: {
flexDirection: 'row',
gap: spacing.md,
heroSection: {
position: 'relative',
height: 380,
marginBottom: spacing.xl,
},
statCard: {
heroImage: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
opacity: 0.21,
},
heroOverlay: {
flex: 1,
backgroundColor: colors.card,
padding: spacing.lg,
borderRadius: borderRadius.lg,
borderWidth: 1,
borderColor: colors.border,
},
statsContainer: {
flex: 1,
justifyContent: '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: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
marginBottom: spacing.xs,
},
statNumber: {
fontSize: 32,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
textTransform: 'uppercase',
letterSpacing: 0.5,
textShadowColor: 'rgba(0, 0, 0, 0.1)',
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 2,
fontWeight: '600',
},
buttonGroup: {
gap: spacing.md,
marginBottom: spacing.xl,
paddingHorizontal: spacing.lg,
},
primaryButton: {
backgroundColor: colors.primaryLight,
backgroundColor: '#8B9A7C', // Sage green from palette
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
borderRadius: borderRadius.lg,
alignItems: 'center',
// Floating effect
shadowColor: '#000000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
},
primaryButtonText: {
fontSize: typography.fontSize.md,
@ -338,6 +380,7 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.md,
paddingHorizontal: spacing.lg,
},
sectionTitle: {
fontSize: typography.fontSize.lg,
@ -351,6 +394,7 @@ const styles = StyleSheet.create({
},
projectsList: {
gap: spacing.md,
paddingHorizontal: spacing.lg,
},
projectItem: {
flexDirection: 'row',
@ -359,8 +403,9 @@ const styles = StyleSheet.create({
backgroundColor: colors.card,
padding: spacing.md,
borderRadius: borderRadius.lg,
borderWidth: 1,
borderWidth: 2,
borderColor: colors.border,
...shadows.md,
},
projectItemLeft: {
flexDirection: 'row',

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import * as ImagePicker from 'expo-image-picker';
import { MaterialCommunityIcons } from '@expo/vector-icons'; // NEW
import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../navigation/types';
@ -22,7 +23,7 @@ import {
getStepsByProject,
} from '../lib/db/repositories';
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 { sortStepsByLogicalOrder, suggestNextStep } from '../lib/utils/stepOrdering';
import { useAuth } from '../contexts/AuthContext';
@ -57,9 +58,15 @@ export const ProjectDetailScreen: React.FC = () => {
console.log('ProjectDetail focused - reloading steps');
loadSteps();
}
}, [route.params.projectId, isNew])
}, [isNew])
);
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
});
}, [navigation]);
const loadSteps = async () => {
try {
const projectSteps = await getStepsByProject(route.params.projectId);
@ -216,6 +223,14 @@ export const ProjectDetailScreen: React.FC = () => {
return (
<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
style={styles.container}
contentContainerStyle={styles.content}
@ -231,7 +246,7 @@ export const ProjectDetailScreen: React.FC = () => {
<Image source={{ uri: coverImage }} style={styles.coverImagePreview} />
) : (
<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>
</View>
)}
@ -258,6 +273,7 @@ export const ProjectDetailScreen: React.FC = () => {
styles.statusButton,
styles.statusButtonInProgress,
status === 'in_progress' && styles.statusButtonActive,
status === 'in_progress' && styles.statusButtonInProgressActive,
]}
onPress={() => setStatus('in_progress')}
>
@ -273,6 +289,7 @@ export const ProjectDetailScreen: React.FC = () => {
styles.statusButton,
styles.statusButtonDone,
status === 'done' && styles.statusButtonActive,
status === 'done' && styles.statusButtonDoneActive,
]}
onPress={() => setStatus('done')}
>
@ -289,6 +306,7 @@ export const ProjectDetailScreen: React.FC = () => {
title={isNew ? 'Create Project' : 'Save Changes'}
onPress={handleSave}
loading={saving}
style={styles.sageButton}
/>
</Card>
@ -298,24 +316,34 @@ export const ProjectDetailScreen: React.FC = () => {
<Text style={styles.sectionTitle}>Process Steps</Text>
<Button
title="Add Step"
icon={<MaterialCommunityIcons name="plus" size={20} color={colors.buttonText} />}
onPress={handleAddStep}
size="sm"
style={styles.sageButton}
/>
</View>
{steps.length > 0 && status === 'in_progress' && (() => {
const nextStep = suggestNextStep(steps);
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}>
<Text style={styles.suggestionIcon}>💡</Text>
<Text style={styles.suggestionTitle}>Suggested Next Step</Text>
<MaterialCommunityIcons name="lightbulb-on-outline" size={20} color={colors.primaryDark} />
<Text style={[styles.suggestionTitle, { color: colors.primaryDark }]}>Suggested Next Step</Text>
</View>
<View style={styles.suggestionContent}>
<Text style={styles.suggestionStepIcon}>{stepTypeIcons[nextStep]}</Text>
<Text style={styles.suggestionStepLabel}>{stepTypeLabels[nextStep]}</Text>
<MaterialCommunityIcons name={stepTypeIcons[nextStep] as any} size={24} color={stepTypeColors[nextStep]} />
<Text style={[styles.suggestionStepLabel, { color: colors.text }]}>{stepTypeLabels[nextStep]}</Text>
</View>
</Card>
</TouchableOpacity>
) : null;
})()}
@ -330,7 +358,7 @@ export const ProjectDetailScreen: React.FC = () => {
{/* Timeline dot and line */}
<View style={styles.timelineLeft}>
<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>
{index < steps.length - 1 && <View style={styles.timelineLine} />}
</View>
@ -367,7 +395,8 @@ export const ProjectDetailScreen: React.FC = () => {
title="Delete Project"
onPress={handleDelete}
variant="outline"
style={styles.deleteButton}
style={styles.terracottaButton}
textStyle={styles.terracottaText}
/>
</>
)}
@ -379,7 +408,40 @@ export const ProjectDetailScreen: React.FC = () => {
const styles = StyleSheet.create({
safeArea: {
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: {
flex: 1,
@ -545,9 +607,15 @@ const styles = StyleSheet.create({
statusButtonInProgress: {
borderColor: colors.primary,
},
statusButtonInProgressActive: {
backgroundColor: '#E8E9E4', // Light sage tint
},
statusButtonDone: {
borderColor: colors.success,
},
statusButtonDoneActive: {
backgroundColor: '#D4E8C8', // Light green tint
},
statusButtonArchived: {
borderColor: colors.textTertiary,
},
@ -598,4 +666,15 @@ const styles = StyleSheet.create({
fontWeight: typography.fontWeight.bold,
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!"
actionLabel="New Project"
onAction={handleCreateProject}
buttonStyle={styles.sageButton}
/>
) : (
<FlatList
@ -169,6 +170,7 @@ export const ProjectsScreen: React.FC = () => {
title="New Project"
onPress={handleCreateProject}
size="md"
style={styles.sageButton}
accessibilityLabel="Create new project"
/>
</View>
@ -327,5 +329,15 @@ const styles = StyleSheet.create({
gap: spacing.md,
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 { 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 { useNavigation } from '@react-navigation/native';
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 { Settings } from '../types';
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';
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
@ -18,6 +18,7 @@ export const SettingsScreen: React.FC = () => {
const [settings, setSettings] = useState<Settings>({
unitSystem: 'imperial',
tempUnit: 'F',
weightUnit: 'lb',
analyticsOptIn: false,
});
@ -120,59 +121,63 @@ export const SettingsScreen: React.FC = () => {
<Text style={styles.sectionTitle}>Units</Text>
<View style={styles.setting}>
<View>
<View style={styles.settingHeader}>
<Text style={styles.settingLabel}>Temperature Unit</Text>
<Text style={styles.settingValue}>
{settings.tempUnit === 'F' ? 'Fahrenheit (°F)' : 'Celsius (°C)'}
</Text>
</View>
<Switch
value={settings.tempUnit === 'C'}
onValueChange={(value) => handleToggle('tempUnit', value ? 'C' : 'F')}
trackColor={{ false: colors.border, true: colors.primary }}
thumbColor={settings.tempUnit === 'C' ? colors.background : colors.background}
ios_backgroundColor={colors.border}
/>
<View style={styles.toggleContainer}>
<TouchableOpacity
style={[styles.toggleButton, settings.tempUnit === 'F' && styles.toggleButtonActive]}
onPress={() => handleToggle('tempUnit', 'F')}
>
<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 style={styles.setting}>
<View>
<View style={styles.settingHeader}>
<Text style={styles.settingLabel}>Unit System</Text>
<Text style={styles.settingValue}>
{settings.unitSystem === 'imperial' ? 'Imperial (lb/in)' : 'Metric (kg/cm)'}
</Text>
</View>
<Switch
value={settings.unitSystem === 'metric'}
onValueChange={(value) =>
handleToggle('unitSystem', value ? 'metric' : 'imperial')
}
trackColor={{ false: colors.border, true: colors.primary }}
thumbColor={settings.unitSystem === 'metric' ? colors.background : colors.background}
ios_backgroundColor={colors.border}
/>
<View style={styles.toggleContainer}>
<TouchableOpacity
style={[styles.toggleButton, settings.unitSystem === 'imperial' && styles.toggleButtonActive]}
onPress={() => handleToggle('unitSystem', 'imperial')}
>
<Text style={[styles.toggleText, settings.unitSystem === 'imperial' && styles.toggleTextActive]}>Imperial</Text>
</TouchableOpacity>
<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 style={[styles.setting, styles.noBorder]}>
<View>
<View style={styles.settingHeader}>
<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>
<Switch
value={settings.weightUnit === 'kg' || settings.weightUnit === 'g'}
onValueChange={(value) => {
// Toggle between imperial (lb) and metric (kg)
const newUnit = value ? 'kg' : 'lb';
handleToggle('weightUnit', newUnit);
}}
trackColor={{ false: colors.border, true: colors.primary }}
thumbColor={(settings.weightUnit === 'kg' || settings.weightUnit === 'g') ? colors.background : colors.background}
ios_backgroundColor={colors.border}
/>
<View style={styles.toggleContainer}>
<TouchableOpacity
style={[styles.toggleButton, (settings.weightUnit === 'lb' || settings.weightUnit === 'oz') && styles.toggleButtonActive]}
onPress={() => handleToggle('weightUnit', 'lb')}
>
<Text style={[styles.toggleText, (settings.weightUnit === 'lb' || settings.weightUnit === 'oz') && styles.toggleTextActive]}>lb</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.toggleButton, (settings.weightUnit === 'kg' || settings.weightUnit === 'g') && styles.toggleButtonActive]}
onPress={() => handleToggle('weightUnit', 'kg')}
>
<Text style={[styles.toggleText, (settings.weightUnit === 'kg' || settings.weightUnit === 'g') && styles.toggleTextActive]}>kg</Text>
</TouchableOpacity>
</View>
</View>
</Card>
@ -283,9 +288,8 @@ const styles = StyleSheet.create({
letterSpacing: 0.5,
},
setting: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'column',
alignItems: 'flex-start',
paddingVertical: spacing.md,
borderBottomWidth: 2,
borderBottomColor: colors.border,
@ -297,9 +301,16 @@ const styles = StyleSheet.create({
},
settingValue: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
marginTop: spacing.xs,
},
settingHeader: {
marginBottom: spacing.sm,
width: '100%',
},
settingInfo: {
flex: 1,
paddingRight: spacing.sm,
},
infoRow: {
flexDirection: 'row',
justifyContent: 'space-between',
@ -341,6 +352,35 @@ const styles = StyleSheet.create({
listButton: {
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: {
borderBottomWidth: 0,
},

View File

@ -8,6 +8,7 @@ import {
Platform,
Alert,
ScrollView,
Image,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
@ -82,40 +83,15 @@ export const SignUpScreen: React.FC = () => {
{/* Hero Image */}
<View style={styles.heroContainer}>
<View style={styles.heroImagePlaceholder}>
<Text style={styles.heroEmoji}>🏺</Text>
</View>
<Image
source={require('../../assets/images/app_logo.png')}
style={styles.heroImage}
resizeMode="contain"
/>
</View>
{/* 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
label="Name"
@ -156,8 +132,19 @@ export const SignUpScreen: React.FC = () => {
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 */}
<View style={styles.loginContainer}>
<View style={[styles.loginContainer, { marginTop: spacing.md }]}>
<Text style={styles.loginText}>Already have an account? </Text>
<TouchableOpacity onPress={() => navigation.navigate('Login')}>
<Text style={styles.loginLink}>Log In</Text>
@ -203,16 +190,10 @@ const styles = StyleSheet.create({
alignItems: 'center',
marginVertical: spacing.xl,
},
heroImagePlaceholder: {
heroImage: {
width: 180,
height: 180,
borderRadius: borderRadius.lg,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
},
heroEmoji: {
fontSize: 90,
},
form: {
width: '100%',

View File

@ -1,9 +1,10 @@
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 * as ImagePicker from 'expo-image-picker';
import { useNavigation, useRoute, RouteProp, CommonActions } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { SafeAreaView } from 'react-native-safe-area-context';
import { RootStackParamList } from '../navigation/types';
import {
StepType,
@ -15,26 +16,26 @@ import {
KilnType,
KilnPosition,
GlazeLayer,
GlazePosition,
ApplicationMethod
} from '../types';
import { createStep, getStep, updateStep, deleteStep, getSettings } from '../lib/db/repositories';
import { getConeTemperature, getBisqueCones, getGlazeFiringCones } from '../lib/utils';
import { Button, Input, Card, Picker, WeightInput, ButtonGrid, DimensionsInput, GlazeLayerCard } from '../components';
import { colors, spacing, typography, borderRadius } from '../lib/theme';
import { getConeTemperature } from '../lib/utils';
import { Button, Input, Card, WeightInput, ButtonGrid, DimensionsInput, GlazeLayerCard } from '../components';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { colors, spacing, typography, borderRadius, stepTypeIcons, stepTypeColors, shadows } from '../lib/theme';
import { useAuth } from '../contexts/AuthContext';
type NavigationProp = NativeStackNavigationProp<RootStackParamList, 'StepEditor'>;
type RouteProps = RouteProp<RootStackParamList, 'StepEditor'>;
const STEP_TYPES: { value: StepType; label: string }[] = [
{ value: 'forming', label: '🏺 Forming' },
{ value: 'drying', label: '☀️ Drying' },
{ value: 'bisque_firing', label: '🔥 Bisque Firing' },
{ value: 'trimming', label: '🔧 Trimming' },
{ value: 'glazing', label: '🎨 Glazing' },
{ value: 'glaze_firing', label: 'Glaze Firing' },
{ value: 'misc', label: '📝 Misc' },
{ value: 'forming', label: 'Forming' },
{ value: 'trimming', label: 'Trimming' },
{ value: 'drying', label: 'Drying' },
{ value: 'bisque_firing', label: 'Bisque Firing' },
{ value: 'glazing', label: 'Glazing' },
{ value: 'glaze_firing', label: 'Glaze Firing' },
{ value: 'misc', label: 'Misc' },
];
export const StepEditorScreen: React.FC = () => {
@ -44,14 +45,15 @@ export const StepEditorScreen: React.FC = () => {
const { projectId, stepId } = route.params;
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
const scrollViewRef = useRef<KeyboardAwareScrollView>(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 [photos, setPhotos] = useState<string[]>([]);
@ -66,7 +68,7 @@ export const StepEditorScreen: React.FC = () => {
const [productionMethod, setProductionMethod] = useState<ProductionMethod>('wheel');
const [dimensions, setDimensions] = useState<Dimensions | undefined>(undefined);
// Firing fields (existing + new)
// Firing fields
const [cone, setCone] = useState('');
const [temperature, setTemperature] = useState('');
const [duration, setDuration] = useState('');
@ -77,11 +79,9 @@ export const StepEditorScreen: React.FC = () => {
const [kilnPositionNotes, setKilnPositionNotes] = useState('');
const [firingSchedule, setFiringSchedule] = useState('');
// Glazing fields (multi-layer)
// Glazing fields
const [glazeLayers, setGlazeLayers] = useState<GlazeLayer[]>([]);
const [customGlazeName, setCustomGlazeName] = useState('');
// Legacy glazing fields (for backwards compatibility)
const [coats, setCoats] = useState('2');
const [applicationMethod, setApplicationMethod] = useState<ApplicationMethod>('brush');
const [selectedGlazeIds, setSelectedGlazeIds] = useState<string[]>([]);
@ -90,9 +90,6 @@ export const StepEditorScreen: React.FC = () => {
const [saving, setSaving] = useState(false);
const [loading, setLoading] = useState(isEditing);
// Track if we're adding glazes from catalog (new flow)
const [isAddingFromCatalog, setIsAddingFromCatalog] = useState(false);
// Load user's preferences
useEffect(() => {
loadUserPreferences();
@ -117,127 +114,15 @@ export const StepEditorScreen: React.FC = () => {
}, [stepId]);
// 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);
// TEMPORARILY DISABLED - Auto-save conflicts with new glaze layer flow
// 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
// Auto-save whenever any field changes
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;
// Only auto-save for existing steps that have been edited
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 () => {
try {
// Prepare data...
const stepData: any = {
projectId,
type: stepType,
@ -279,39 +164,15 @@ export const StepEditorScreen: React.FC = () => {
} catch (error) {
console.error('Auto-save error:', error);
}
}, 1000); // Wait 1 second after last change
}, 1000);
return () => clearTimeout(timeoutId);
}
}, [
stepType,
notes,
photos,
// Forming fields
clayBody,
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,
stepType, notes, photos,
clayBody, clayWeight, productionMethod, dimensions,
cone, temperature, duration, kilnNotes, kilnType, kilnCustomName, kilnPosition, kilnPositionNotes, firingSchedule,
glazeLayers, coats, applicationMethod, selectedGlazeIds, mixNotes,
isEditing, stepId, loading, projectId, tempUnit
]);
const loadStep = async () => {
@ -322,7 +183,6 @@ export const StepEditorScreen: React.FC = () => {
navigation.goBack();
return;
}
setStepType(step.type);
setNotes(step.notesMarkdown || '');
setPhotos(step.photoUris || []);
@ -351,11 +211,9 @@ export const StepEditorScreen: React.FC = () => {
} else if (step.type === 'glazing') {
const glazing = (step as any).glazing;
if (glazing) {
// New multi-layer format
if (glazing.glazeLayers && glazing.glazeLayers.length > 0) {
setGlazeLayers(glazing.glazeLayers);
} else {
// Legacy format - keep for compatibility
setCoats(glazing.coats?.toString() || '2');
setApplicationMethod(glazing.application || 'brush');
setSelectedGlazeIds(glazing.glazeIds || []);
@ -373,7 +231,7 @@ export const StepEditorScreen: React.FC = () => {
const handleConeChange = (value: string) => {
setCone(value);
const temp = getConeTemperature(value, tempUnit); // Pass user's preferred unit
const temp = getConeTemperature(value, tempUnit);
if (temp) {
setTemperature(temp.value.toString());
}
@ -385,13 +243,11 @@ export const StepEditorScreen: React.FC = () => {
Alert.alert('Permission needed', 'We need camera permissions to take photos');
return;
}
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [4, 3],
quality: 0.8,
});
if (!result.canceled && result.assets[0]) {
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');
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: 'images',
allowsEditing: true,
aspect: [4, 3],
quality: 0.8,
});
if (!result.canceled && result.assets[0]) {
setPhotos([...photos, result.assets[0].uri]);
}
};
const handleAddPhoto = () => {
Alert.alert(
'Add Photo',
'Choose a source',
[
Alert.alert('Add Photo', 'Choose a source', [
{ text: 'Camera', onPress: takePhoto },
{ text: 'Gallery', onPress: pickImage },
{ text: 'Cancel', style: 'cancel' },
]
);
]);
};
const removePhoto = (index: number) => {
@ -433,19 +283,11 @@ export const StepEditorScreen: React.FC = () => {
};
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[]) => {
console.log('Callback received layers:', newLayers);
setGlazeLayers(prevLayers => [...prevLayers, ...newLayers]);
if (stepType !== 'glazing') {
console.log('Setting stepType to glazing');
setStepType('glazing');
}
// Scroll to glaze layers section after state update
setTimeout(() => {
if (glazeLayersSectionRef.current && scrollViewRef.current) {
glazeLayersSectionRef.current.measure((x, y, width, height, pageX, pageY) => {
@ -456,7 +298,6 @@ export const StepEditorScreen: React.FC = () => {
}
}, 200);
};
navigation.navigate('GlazePicker', {
projectId,
stepId,
@ -497,7 +338,6 @@ export const StepEditorScreen: React.FC = () => {
} else if (stepType === 'glazing') {
stepData.glazing = {
glazeLayers: glazeLayers.length > 0 ? glazeLayers : undefined,
// Legacy fields for backwards compatibility
glazeIds: selectedGlazeIds.length > 0 ? selectedGlazeIds : undefined,
coats: coats ? parseInt(coats) : undefined,
application: applicationMethod,
@ -507,20 +347,16 @@ export const StepEditorScreen: React.FC = () => {
if (isEditing) {
await updateStep(stepId!, stepData);
console.log('✅ Step updated successfully');
} else {
const newStep = await createStep(stepData);
console.log('✅ Step created successfully, id:', newStep?.id);
await createStep(stepData);
}
// Reset navigation to ProjectDetail, keeping MainTabs in stack
console.log('Resetting stack to MainTabs → ProjectDetail after manual save');
navigation.dispatch(
CommonActions.reset({
index: 1, // ProjectDetail is the active screen
index: 1,
routes: [
{ name: 'MainTabs' }, // Keep MainTabs in stack for back button
{ name: 'ProjectDetail', params: { projectId } }, // ProjectDetail is active
{ name: 'MainTabs' },
{ name: 'ProjectDetail', params: { projectId } },
],
})
);
@ -533,10 +369,7 @@ export const StepEditorScreen: React.FC = () => {
};
const handleDelete = () => {
Alert.alert(
'Delete Step',
'Are you sure you want to delete this step?',
[
Alert.alert('Delete Step', 'Are you sure you want to delete this step?', [
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete',
@ -544,16 +377,17 @@ export const StepEditorScreen: React.FC = () => {
onPress: async () => {
try {
await deleteStep(stepId!);
Alert.alert('Success', 'Step deleted');
navigation.goBack();
} catch (error) {
console.error('Failed to delete step:', error);
Alert.alert('Error', 'Failed to delete step');
}
},
},
]
);
]);
};
const handleCancel = () => {
navigation.goBack();
};
const renderFormingFields = () => {
@ -572,22 +406,19 @@ export const StepEditorScreen: React.FC = () => {
onChangeText={setClayBody}
placeholder="e.g., B-Mix, Porcelain"
/>
<WeightInput
label="Clay Weight"
value={clayWeight}
onChange={setClayWeight}
userPreferredUnit={weightUnit}
/>
<Text style={styles.label}>Production Method</Text>
<Text style={styles.sectionLabel}>Production Method</Text>
<ButtonGrid
options={productionMethods}
selectedValue={productionMethod}
onSelect={(value) => setProductionMethod(value as ProductionMethod)}
columns={2}
/>
<DimensionsInput
label="Dimensions"
value={dimensions}
@ -605,7 +436,6 @@ export const StepEditorScreen: React.FC = () => {
{ value: 'raku', label: 'Raku' },
{ value: 'other', label: 'Other' },
];
const kilnPositions = [
{ value: 'top', label: 'Top' },
{ value: 'middle', label: 'Middle' },
@ -635,15 +465,13 @@ export const StepEditorScreen: React.FC = () => {
placeholder="Total firing time"
keyboardType="numeric"
/>
<Text style={styles.label}>Kiln Type</Text>
<Text style={styles.sectionLabel}>Kiln Type</Text>
<ButtonGrid
options={kilnTypes}
selectedValue={kilnType}
onSelect={(value) => setKilnType(value as KilnType)}
columns={2}
/>
{kilnType === 'other' && (
<Input
label="Custom Kiln Name"
@ -652,15 +480,13 @@ export const StepEditorScreen: React.FC = () => {
placeholder="Enter kiln name"
/>
)}
<Text style={styles.label}>Position in Kiln</Text>
<Text style={styles.sectionLabel}>Position in Kiln</Text>
<ButtonGrid
options={kilnPositions}
selectedValue={kilnPosition}
onSelect={(value) => setKilnPosition(value as KilnPosition)}
columns={2}
/>
{kilnPosition === 'other' && (
<Input
label="Position Notes"
@ -669,7 +495,6 @@ export const StepEditorScreen: React.FC = () => {
placeholder="Describe position"
/>
)}
<Input
label="Firing Schedule"
value={firingSchedule}
@ -678,7 +503,6 @@ export const StepEditorScreen: React.FC = () => {
multiline
numberOfLines={4}
/>
<Input
label="Kiln Notes"
value={kilnNotes}
@ -693,7 +517,6 @@ export const StepEditorScreen: React.FC = () => {
const renderGlazingFields = () => {
const handleAddCustomGlaze = () => {
if (!customGlazeName.trim()) return;
const newLayer: GlazeLayer = {
customGlazeName: customGlazeName.trim(),
application: 'brush',
@ -701,7 +524,6 @@ export const StepEditorScreen: React.FC = () => {
coats: 2,
notes: '',
};
setGlazeLayers([...glazeLayers, newLayer]);
setCustomGlazeName('');
};
@ -719,8 +541,7 @@ export const StepEditorScreen: React.FC = () => {
return (
<>
<View ref={glazeLayersSectionRef}>
<Text style={styles.label}>Glaze Layers</Text>
<Text style={styles.sectionLabel}>Glaze Layers</Text>
{glazeLayers.map((layer, index) => (
<GlazeLayerCard
key={index}
@ -733,7 +554,7 @@ export const StepEditorScreen: React.FC = () => {
</View>
<View style={styles.addGlazeContainer}>
<Text style={styles.label}>Add Custom Glaze</Text>
<Text style={styles.sectionLabel}>Add Custom Glaze</Text>
<View style={styles.customGlazeRow}>
<TextInput
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 (
<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
ref={scrollViewRef}
style={styles.container}
@ -787,25 +628,19 @@ export const StepEditorScreen: React.FC = () => {
keyboardShouldPersistTaps="handled"
>
<Card>
<Text style={styles.label}>Step Type</Text>
<View style={styles.typeGrid}>
{STEP_TYPES.map((type) => (
<Button
key={type.value}
title={type.label}
onPress={() => setStepType(type.value)}
variant={stepType === type.value ? 'primary' : 'outline'}
size="sm"
style={styles.typeButton}
<Text style={styles.sectionLabel}>Step Type</Text>
<ButtonGrid
options={stepTypeOptions}
selectedValue={stepType}
onSelect={(value) => setStepType(value as StepType)}
columns={2}
/>
))}
</View>
{stepType === 'forming' && renderFormingFields()}
{(stepType === 'bisque_firing' || stepType === 'glaze_firing') && renderFiringFields()}
{stepType === 'glazing' && renderGlazingFields()}
<Text style={styles.label}>Photos</Text>
<Text style={styles.sectionLabel}>Photos</Text>
<View style={styles.photosContainer}>
{photos.map((uri, index) => (
<View key={index} style={styles.photoWrapper}>
@ -856,10 +691,51 @@ export const StepEditorScreen: React.FC = () => {
)}
</Card>
</KeyboardAwareScrollView>
</SafeAreaView>
);
};
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: {
flex: 1,
backgroundColor: colors.backgroundSecondary,
@ -868,22 +744,14 @@ const styles = StyleSheet.create({
padding: spacing.md,
paddingBottom: spacing.xxl,
},
label: {
sectionLabel: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.sm,
letterSpacing: 0.5,
textTransform: 'uppercase',
},
typeGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.sm,
marginBottom: spacing.lg,
},
typeButton: {
minWidth: '30%',
marginTop: spacing.md,
},
note: {
fontSize: typography.fontSize.sm,
@ -891,6 +759,9 @@ const styles = StyleSheet.create({
fontStyle: 'italic',
marginTop: spacing.sm,
},
typeGrid: {
marginBottom: 120, // Much larger separation as requested
},
photosContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
@ -951,44 +822,6 @@ const styles = StyleSheet.create({
deleteButtonInSticky: {
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: {
marginBottom: spacing.md,
},
@ -1018,11 +851,4 @@ const styles = StyleSheet.create({
marginTop: spacing.xs,
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