import { Pressable, View, Dimensions } from 'react-native'; import { Image } from 'expo-image'; import { router } from 'expo-router'; import ContextMenu from 'react-native-context-menu-view'; import { Ionicons } from '@expo/vector-icons'; import { useTheme } from '~/contexts/ThemeContext'; import { Text } from '~/components/Text'; import { Button } from '~/components/Button'; import { Tag } from '~/store/tagStore'; import { usePromptStore } from '~/store/promptStore'; import { LAYOUT } from '~/constants'; import { getThumbnailUrl, getSizeForViewMode } from '~/utils/image'; const { width } = Dimensions.get('window'); type ImageCardProps = { id: string; publicUrl: string | null; prompt: string; createdAt: string; isFavorite?: boolean; model?: string; tags?: Tag[]; viewMode: 'single' | 'grid3' | 'grid5'; blurhash?: string | null; isGenerating?: boolean; // New prop for generating state // Gallery mode props onToggleFavorite?: () => void; // Explore mode props creatorUsername?: string; likesCount?: number; userHasLiked?: boolean; onToggleLike?: () => void; }; export function ImageCard({ id, publicUrl, prompt, createdAt, isFavorite, model, tags, viewMode, blurhash, isGenerating, onToggleFavorite, creatorUsername, likesCount, userHasLiked, onToggleLike, }: ImageCardProps) { const { theme } = useTheme(); const { setPrompt } = usePromptStore(); const isSingleColumn = viewMode === 'single'; const isGalleryMode = !!onToggleFavorite; // Get appropriate thumbnail URL based on view mode const thumbnailUrl = getThumbnailUrl(publicUrl, getSizeForViewMode(viewMode)); // Get tiny thumbnail for progressive loading (blur-up effect) const tinyThumbnailUrl = getThumbnailUrl(publicUrl, 'tiny'); // Calculate image size based on view mode const getImageSize = () => { const spacing = 4; // Minimal spacing between items switch (viewMode) { case 'single': return width - spacing * 2; // Minimal outer padding case 'grid3': return (width - spacing * 4) / 3; // Left + right + 2 gaps case 'grid5': return (width - spacing * 6) / 5; // Left + right + 4 gaps default: return width - spacing * 2; } }; const imageSize = getImageSize(); // Format model name for display const formatModelName = (modelName?: string) => { if (!modelName) return 'Unbekannt'; // Remove common prefixes and clean up const cleaned = modelName .replace(/^(black-forest-labs\/|bytedance\/|lucataco\/|stability-ai\/)/, '') .replace(/-/g, ' ') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); return cleaned; }; // Context menu actions const contextMenuActions = isGalleryMode ? [ { title: isFavorite ? 'Von Favoriten entfernen' : 'Zu Favoriten hinzufügen', systemIcon: isFavorite ? 'heart.fill' : 'heart', }, { title: 'Teilen', systemIcon: 'square.and.arrow.up', }, { title: 'Details anzeigen', systemIcon: 'info.circle', }, { title: new Date(createdAt).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }), systemIcon: 'calendar', disabled: true, }, { title: formatModelName(model), systemIcon: 'cpu', disabled: true, }, ] : [ // Explore mode actions { title: userHasLiked ? 'Like entfernen' : 'Liken', systemIcon: userHasLiked ? 'heart.fill' : 'heart', }, { title: 'Teilen', systemIcon: 'square.and.arrow.up', }, { title: 'Details anzeigen', systemIcon: 'info.circle', }, { title: `von ${creatorUsername || 'Anonym'}`, systemIcon: 'person', disabled: true, }, { title: new Date(createdAt).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }), systemIcon: 'calendar', disabled: true, }, { title: formatModelName(model), systemIcon: 'cpu', disabled: true, }, ]; const handleContextMenu = (e: any) => { const index = e.nativeEvent.index; if (isGalleryMode) { switch (index) { case 0: // Toggle favorite onToggleFavorite?.(); break; case 1: // Share // TODO: Implement share functionality console.log('Share image:', id); break; case 2: // View details router.push(`/image/${id}`); break; } } else { // Explore mode switch (index) { case 0: // Toggle like onToggleLike?.(); break; case 1: // Share // TODO: Implement share functionality console.log('Share image:', id); break; case 2: // View details router.push(`/image/${id}`); break; } } }; const CardContent = isSingleColumn ? ( // Single Column: Image and info as separate elements router.push(`/image/${id}`)} > {/* Image Container */} {/* Show loading state for generating images */} {isGenerating ? ( Generiere... ) : thumbnailUrl ? ( ) : ( {prompt.substring(0, 50)}... )} {/* Info below image - outside the image container */} {/* Prompt with Author/Favorite/Likes */} { if (isGalleryMode) { e.stopPropagation(); setPrompt(prompt); } }} style={{ flex: 1, marginRight: 8 }} > {prompt} {/* Gallery Mode: Favorite Badge */} {isGalleryMode && isFavorite && ( )} {/* Explore Mode: Author Name and Likes */} {!isGalleryMode && ( {creatorUsername || 'Anonym'} )} ) : ( // Grid View: Original layout with overlays router.push(`/image/${id}`)} > {/* Show loading state for generating images */} {isGenerating ? ( {viewMode !== 'grid5' && ( Generiere... )} ) : thumbnailUrl ? ( ) : ( {prompt.substring(0, 50)}... )} {/* Explore Mode Grid: Creator and Like Info - hide in grid5 */} {!isGalleryMode && viewMode !== 'grid5' && ( {creatorUsername || 'Anonym'} )} {/* Favorite Badge - Gallery Mode Only - Top Right */} {isGalleryMode && isFavorite && ( )} {/* Tags Preview - only in grid mode, hide in grid5 to save space */} {viewMode !== 'grid5' && tags && tags.length > 0 && ( {tags.slice(0, viewMode === 'grid3' ? 1 : 2).map(tag => ( #{tag.name} ))} {tags.length > (viewMode === 'grid3' ? 1 : 2) && ( +{tags.length - (viewMode === 'grid3' ? 1 : 2)} )} )} ); // Wrap all cards with context menu (both gallery and explore mode) return ( {CardContent} ); }