This commit is contained in:
2025-10-29 00:11:51 +01:00
parent 8dab9bfd25
commit 31dfe1dac5
7 changed files with 379 additions and 9 deletions

View File

@@ -0,0 +1,195 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
interface PotteryIconProps {
color: string;
finish?: 'glossy' | 'matte' | 'satin' | 'unknown';
size?: number;
}
export const PotteryIcon: React.FC<PotteryIconProps> = ({
color,
finish = 'unknown',
size = 60
}) => {
// Different pottery shapes based on finish type using View components
const renderShape = () => {
const shapeStyle = {
backgroundColor: color,
width: size,
height: size,
};
switch (finish) {
case 'glossy':
// Vase shape - wider at bottom, narrower at top
return (
<View style={{ alignItems: 'center' }}>
<View style={[styles.vaseTop, { width: size * 0.4, height: size * 0.15, backgroundColor: color }]} />
<View style={[styles.vaseNeck, { width: size * 0.5, height: size * 0.25, backgroundColor: color }]} />
<View style={[styles.vaseBody, { width: size * 0.8, height: size * 0.5, backgroundColor: color }]} />
<View style={[styles.vaseBase, { width: size * 0.6, height: size * 0.1, backgroundColor: color }]} />
{/* Glossy shine effect */}
<View style={[styles.shine, { top: size * 0.2, left: size * 0.2 }]} />
</View>
);
case 'matte':
// Bowl shape - smooth trapezoid with many layers
const numLayers = 20;
const startWidth = 0.9; // Top width (90% of size)
const endWidth = 0.4; // Bottom width (40% of size)
const layerHeight = size / numLayers;
return (
<View style={{ alignItems: 'center', justifyContent: 'center', height: size, width: size }}>
{Array.from({ length: numLayers }).map((_, index) => {
// Calculate width for this layer (gradually narrowing from top to bottom)
const progress = index / (numLayers - 1);
const layerWidth = startWidth - (startWidth - endWidth) * progress;
const isFirst = index === 0;
const isLast = index === numLayers - 1;
return (
<View
key={index}
style={[
isFirst ? styles.bowlTop : isLast ? styles.bowlBase : styles.bowlLayer,
{
width: size * layerWidth,
height: layerHeight,
backgroundColor: color,
borderTopLeftRadius: isFirst ? size * 0.15 : 0,
borderTopRightRadius: isFirst ? size * 0.15 : 0,
borderBottomLeftRadius: isLast ? size * 0.08 : 0,
borderBottomRightRadius: isLast ? size * 0.08 : 0,
}
]}
/>
);
})}
</View>
);
case 'satin':
// Mug shape - cylinder with handle
return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View style={{ alignItems: 'center' }}>
<View style={[styles.mugRim, { width: size * 0.6, height: size * 0.1, backgroundColor: color }]} />
<View style={[styles.mugBody, { width: size * 0.6, height: size * 0.7, backgroundColor: color }]} />
<View style={[styles.mugBase, { width: size * 0.6, height: size * 0.2, backgroundColor: color }]} />
</View>
<View style={[styles.handle, { borderColor: color, marginLeft: -size * 0.1 }]} />
</View>
);
default:
// Simple circular swatch for unknown finish
return (
<View style={[styles.circle, shapeStyle]} />
);
}
};
return (
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
{renderShape()}
</View>
);
};
const styles = StyleSheet.create({
// Vase styles
vaseTop: {
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
borderWidth: 1,
borderColor: '#8B7355',
},
vaseNeck: {
borderLeftWidth: 1,
borderRightWidth: 1,
borderColor: '#8B7355',
},
vaseBody: {
borderLeftWidth: 1,
borderRightWidth: 1,
borderColor: '#8B7355',
borderRadius: 8,
},
vaseBase: {
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
borderWidth: 1,
borderColor: '#8B7355',
},
shine: {
position: 'absolute',
width: 8,
height: 12,
backgroundColor: 'rgba(255, 255, 255, 0.4)',
borderRadius: 4,
},
// Bowl styles - trapezoid bowl shape
bowlTop: {
borderTopWidth: 2,
borderLeftWidth: 2,
borderRightWidth: 2,
borderColor: '#8B7355',
marginTop: -1, // Overlap to connect
},
bowlLayer: {
borderLeftWidth: 2,
borderRightWidth: 2,
borderColor: '#8B7355',
marginTop: -1, // Overlap to connect smoothly
},
bowlBase: {
borderBottomWidth: 2,
borderLeftWidth: 2,
borderRightWidth: 2,
borderColor: '#8B7355',
marginTop: -1, // Overlap to connect
},
// Mug styles
mugRim: {
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
borderTopWidth: 2,
borderLeftWidth: 1,
borderRightWidth: 1,
borderColor: '#8B7355',
},
mugBody: {
borderLeftWidth: 1,
borderRightWidth: 1,
borderColor: '#8B7355',
},
mugBase: {
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
borderBottomWidth: 2,
borderLeftWidth: 1,
borderRightWidth: 1,
borderColor: '#8B7355',
},
handle: {
width: 15,
height: 25,
borderWidth: 2,
borderLeftWidth: 0,
borderRadius: 8,
backgroundColor: 'transparent',
},
// Default circle
circle: {
borderRadius: 100,
borderWidth: 2,
borderColor: '#8B7355',
},
});

View File

@@ -1,3 +1,4 @@
export * from './Button';
export * from './Card';
export * from './Input';
export * from './PotteryIcon';

View File

@@ -14,7 +14,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../navigation/types';
import { Glaze } from '../types';
import { getAllGlazes, searchGlazes } from '../lib/db/repositories';
import { Button, Card } from '../components';
import { Button, Card, PotteryIcon } from '../components';
import { colors, spacing, typography, borderRadius } from '../lib/theme';
import { mixColors, generateMixName } from '../lib/utils';
import { useAuth } from '../contexts/AuthContext';
@@ -128,8 +128,14 @@ export const GlazeMixerScreen: React.FC = () => {
<TouchableOpacity onPress={() => toggleGlaze(item)}>
<Card style={[styles.glazeCard, isSelected ? styles.glazeCardSelected : null]}>
<View style={styles.glazeMainContent}>
{item.color && (
<View style={[styles.colorPreview, { backgroundColor: item.color }]} />
{item.color ? (
<PotteryIcon
color={item.color}
finish={item.finish}
size={60}
/>
) : (
<View style={styles.colorPreview} />
)}
<View style={styles.glazeInfo}>
<View style={styles.glazeHeader}>
@@ -161,7 +167,11 @@ export const GlazeMixerScreen: React.FC = () => {
<Card style={styles.mixPreviewCard}>
<Text style={styles.mixPreviewTitle}>Mix Preview</Text>
<View style={styles.mixPreviewContent}>
<View style={[styles.mixedColorPreview, { backgroundColor: getMixedColor() }]} />
<PotteryIcon
color={getMixedColor()}
finish="glossy"
size={80}
/>
<View style={styles.mixInfo}>
<Text style={styles.mixedGlazesText}>
{selectedGlazes.map(g => g.name).join(' + ')}

View File

@@ -13,7 +13,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../navigation/types';
import { Glaze } from '../types';
import { getAllGlazes, searchGlazes } from '../lib/db/repositories';
import { Button, Card } from '../components';
import { Button, Card, PotteryIcon } from '../components';
import { colors, spacing, typography, borderRadius } from '../lib/theme';
import { useAuth } from '../contexts/AuthContext';
@@ -100,8 +100,14 @@ export const GlazePickerScreen: React.FC = () => {
<TouchableOpacity onPress={() => toggleGlaze(item.id)}>
<Card style={[styles.glazeCard, isSelected ? styles.glazeCardSelected : null]}>
<View style={styles.glazeMainContent}>
{item.color && (
<View style={[styles.colorPreview, { backgroundColor: item.color }]} />
{item.color ? (
<PotteryIcon
color={item.color}
finish={item.finish}
size={60}
/>
) : (
<View style={styles.colorPreview} />
)}
<View style={styles.glazeInfo}>
<View style={styles.glazeHeader}>

View File

@@ -281,7 +281,6 @@ const styles = StyleSheet.create({
backgroundColor: colors.background,
},
header: {
paddingHorizontal: spacing.lg,
paddingTop: 64,
paddingBottom: spacing.lg,
backgroundColor: colors.background,
@@ -294,6 +293,7 @@ const styles = StyleSheet.create({
color: colors.primary,
letterSpacing: 0.5,
textTransform: 'uppercase',
paddingHorizontal: spacing.lg,
},
searchContainer: {
flexDirection: 'row',
@@ -359,10 +359,10 @@ const styles = StyleSheet.create({
color: colors.text,
},
listContent: {
paddingHorizontal: spacing.md,
paddingBottom: 100, // Extra space for floating tab bar
},
projectCard: {
marginHorizontal: spacing.md,
marginBottom: spacing.md,
},
imageContainer: {