fertig
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 366 KiB |
BIN
assets/icon.png
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 366 KiB |
|
After Width: | Height: | Size: 366 KiB |
|
After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 737 KiB |
|
After Width: | Height: | Size: 687 KiB |
|
After Width: | Height: | Size: 784 KiB |
|
After Width: | Height: | Size: 601 KiB |
|
After Width: | Height: | Size: 647 KiB |
|
After Width: | Height: | Size: 642 KiB |
|
After Width: | Height: | Size: 860 KiB |
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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> = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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%',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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%',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||