import React, { useRef, useState } from "react"; import { Pressable, ScrollView, Text, TextInput, View, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import * as Haptics from "expo-haptics"; import ViewShot from "react-native-view-shot"; import { CanvasLayerItem } from "./CanvasLayerItem"; import { CanvasQrItem } from "./CanvasQrItem"; import { InstagramTextEditorModal } from "./InstagramTextEditorModal"; import { type CanvasTextLayer, type CanvasQrConfig, CANVAS_FONT_OPTIONS, QR_SIZE_PRESETS, QR_RADIUS_PRESETS, getDefaultLayersForTemplate, getDefaultQrConfigForTemplate, } from "./types"; interface QrCanvasStudioProps { templateKey: string; restaurantName: string; qrBase64?: string; width: number; height: number; renderBackground: () => React.ReactNode; layers: CanvasTextLayer[]; onLayersChange: React.Dispatch>; qrConfig: CanvasQrConfig; onQrConfigChange: React.Dispatch>; viewShotRef?: React.RefObject; selectedLayerId: string | null; onSelectLayer: (id: string | null) => void; isQrSelected: boolean; onSelectQr: (selected: boolean) => void; onDragStateChange?: (isDragging: boolean) => void; } export const QrCanvasStudio: React.FC = ({ templateKey, restaurantName, qrBase64, width, height, renderBackground, layers, onLayersChange, qrConfig, onQrConfigChange, viewShotRef, selectedLayerId, onSelectLayer, isQrSelected, onSelectQr, onDragStateChange, }) => { const selectedLayer = layers.find((l) => l.id === selectedLayerId) || null; // Instagram Metin Düzenleyici Modal Durumu const [editingLayer, setEditingLayer] = useState(null); const [isTextModalOpen, setIsTextModalOpen] = useState(false); // Instagram/Figma Kılavuz Çizgileri const [guideLines, setGuideLines] = useState<{ verticalCenter: boolean; horizontalCenter: boolean; }>({ verticalCenter: false, horizontalCenter: false }); const lastVRef = useRef(false); const lastHRef = useRef(false); // Sürükleme Başlangıcı const handleDragStart = () => { onDragStateChange?.(true); }; // Manyetik Yapışma (Snap) & Kılavuz Çizgileri const handleSnap = (rawX: number, rawY: number, itemWidth: number, itemHeight: number) => { const SNAP_THRESHOLD = 6; let snappedX = Math.round(rawX); let snappedY = Math.round(rawY); let vCenter = false; let hCenter = false; // 1. Dikey Kılavuz (Yatayda Tam Ortalandığında) const itemCenterX = rawX + itemWidth / 2; const canvasCenterX = width / 2; if (Math.abs(itemCenterX - canvasCenterX) <= SNAP_THRESHOLD) { snappedX = Math.round(canvasCenterX - itemWidth / 2); vCenter = true; } // 2. Yatay Kılavuz (Dikeyde Tam Ortalandığında) const itemCenterY = rawY + itemHeight / 2; const canvasCenterY = height / 2; if (Math.abs(itemCenterY - canvasCenterY) <= SNAP_THRESHOLD) { snappedY = Math.round(canvasCenterY - itemHeight / 2); hCenter = true; } // Dokunsal Titreşim if ((vCenter && !lastVRef.current) || (hCenter && !lastHRef.current)) { try { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } catch {} } lastVRef.current = vCenter; lastHRef.current = hCenter; setGuideLines({ verticalCenter: vCenter, horizontalCenter: hCenter }); return { snappedX, snappedY }; }; // Sürükleme Bitişi const handleDragEnd = () => { setGuideLines({ verticalCenter: false, horizontalCenter: false }); lastVRef.current = false; lastHRef.current = false; onDragStateChange?.(false); }; // Metin Katmanı Pozisyon Güncelleme const handleUpdateLayerPosition = (id: string, x: number, y: number) => { onLayersChange((prev) => prev.map((l) => (l.id === id ? { ...l, x, y } : l)) ); }; // QR Kod Pozisyon Güncelleme const handleUpdateQrPosition = (x: number, y: number) => { onQrConfigChange((prev) => ({ ...prev, x, y })); }; // QR Kodu Yatayda Ortala const handleCenterQrHorizontally = () => { const centeredX = Math.round((width - qrConfig.size) / 2); onQrConfigChange((prev) => ({ ...prev, x: centeredX })); try { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } catch {} }; // Seçili Metni Yatayda Ortala const handleCenterLayerHorizontally = (id: string) => { const target = layers.find((l) => l.id === id); if (!target) return; const approxW = 80; const centeredX = Math.round((width - approxW) / 2); onLayersChange((prev) => prev.map((l) => (l.id === id ? { ...l, x: centeredX } : l)) ); try { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } catch {} }; // Yeni Metin Katmanı Ekle (Instagram "Aa" butonu) const handleAddTextLayer = () => { try { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); } catch {} const newLayer: CanvasTextLayer = { id: `layer_${Date.now()}`, text: "Yeni Metin", x: Math.round(width / 2 - 45), y: Math.round(height / 2 - 20), rotation: 0, fontSize: 16, fontId: "sans", color: "#FFFFFF", bgBoxColor: "rgba(18, 18, 21, 0.88)", fontWeight: "800", textAlign: "center", }; onLayersChange((prev) => [...prev, newLayer]); onSelectQr(false); onSelectLayer(newLayer.id); // Doğrudan Instagram Text Editor'ı aç setEditingLayer(newLayer); setIsTextModalOpen(true); }; // Katman Düzenle Modalını Aç const handleOpenTextEditor = (layer: CanvasTextLayer) => { setEditingLayer(layer); setIsTextModalOpen(true); }; // Katman Düzenleme Kaydet const handleSaveTextEditor = (updated: Partial) => { if (!editingLayer) return; onLayersChange((prev) => prev.map((l) => (l.id === editingLayer.id ? { ...l, ...updated } : l)) ); }; // Katman Sil const handleDeleteLayer = (id: string) => { try { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); } catch {} onLayersChange((prev) => prev.filter((l) => l.id !== id)); if (selectedLayerId === id) { onSelectLayer(null); } }; // Katman Çoğalt const handleDuplicateLayer = (id: string) => { try { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } catch {} const target = layers.find((l) => l.id === id); if (!target) return; const duplicated: CanvasTextLayer = { ...target, id: `layer_${Date.now()}`, x: Math.min(width - 50, target.x + 12), y: Math.min(height - 30, target.y + 12), }; onLayersChange((prev) => [...prev, duplicated]); onSelectQr(false); onSelectLayer(duplicated.id); }; // Şablonu Sıfırla const handleResetLayers = () => { try { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); } catch {} const defaultLayers = getDefaultLayersForTemplate(templateKey, restaurantName); const defaultQr = getDefaultQrConfigForTemplate(templateKey, width, height); onLayersChange(defaultLayers); onQrConfigChange(defaultQr); onSelectQr(false); onSelectLayer(null); }; return ( {/* 1. INSTAGRAM STORY / CANVA STİLİ ÜST HIZLI ARAÇLAR */} {/* Metin Ekle (+ Aa) */} ({ backgroundColor: "#D4AF37", paddingHorizontal: 14, paddingVertical: 8, borderRadius: 20, flexDirection: "row", alignItems: "center", gap: 6, opacity: pressed ? 0.8 : 1, })} > + Aa Metin {/* QR Seçici / Ayar */} { try { Haptics.selectionAsync(); } catch {} onSelectLayer(null); onSelectQr(!isQrSelected); }} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={({ pressed }) => ({ backgroundColor: isQrSelected ? "#D4AF37" : "#18181B", borderWidth: 1, borderColor: isQrSelected ? "#D4AF37" : "rgba(255,255,255,0.12)", paddingHorizontal: 12, paddingVertical: 8, borderRadius: 20, flexDirection: "row", alignItems: "center", gap: 5, opacity: pressed ? 0.8 : 1, })} > QR Kodu {/* Sağ: Sıfırla */} ({ backgroundColor: "#18181B", borderWidth: 1, borderColor: "rgba(255,255,255,0.1)", paddingHorizontal: 10, paddingVertical: 7, borderRadius: 16, flexDirection: "row", alignItems: "center", gap: 4, opacity: pressed ? 0.8 : 1, })} > Sıfırla {/* 2. TUVAL ALANI (INSTAGRAM KORUYUCU DOKUNMATİK ÇERÇEVE) */} { // Tuvalin boş alanına dokunulduğunda seçimi kaldır (temiz önizleme) onSelectLayer(null); onSelectQr(false); }} style={{ width, height, borderRadius: 24, overflow: "hidden", backgroundColor: "#0C0C0E", shadowColor: "#000", shadowOffset: { width: 0, height: 10 }, shadowOpacity: 0.5, shadowRadius: 20, elevation: 8, marginBottom: 14, position: "relative", }} > {/* Arka Plan Şablonu */} {renderBackground()} {/* Sürüklenebilir & Boyutlandırılabilir QR Kod */} { try { Haptics.selectionAsync(); } catch {} onSelectLayer(null); onSelectQr(true); }} onUpdatePosition={handleUpdateQrPosition} onDragStart={handleDragStart} onDragMove={handleSnap} onDragEnd={handleDragEnd} canvasWidth={width} canvasHeight={height} /> {/* Sürüklenebilir & Çift Dokunmalı Metin Katmanları */} {layers.map((layer) => ( { try { Haptics.selectionAsync(); } catch {} onSelectQr(false); onSelectLayer(id); }} onEdit={handleOpenTextEditor} onUpdatePosition={handleUpdateLayerPosition} onDelete={handleDeleteLayer} onDragStart={handleDragStart} onDragMove={handleSnap} onDragEnd={handleDragEnd} canvasWidth={width} canvasHeight={height} /> ))} {/* 📐 FIGMA / INSTAGRAM MANYETİK KILAVUZ ÇİZGİLERİ */} {guideLines.verticalCenter && ( )} {guideLines.horizontalCenter && ( )} {/* 3. SEÇİLİ ÖĞE İÇİN INSTAGRAM TARZI YÜZEN HIZLI ARAÇLAR (FLOATING DOCK) */} {/* 3A: SEÇİLİ METİN KATMANI ARAÇLARI */} {selectedLayer && !isQrSelected && ( {/* Üst Sıra: Hızlı Butonlar */} SEÇİLİ METİN {/* Düzenle (Aa) Modalını Aç */} handleOpenTextEditor(selectedLayer)} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={{ backgroundColor: "#D4AF37", paddingHorizontal: 12, paddingVertical: 5, borderRadius: 14, flexDirection: "row", alignItems: "center", gap: 4, }} > Aa Düzenle {/* Yatayda Ortala */} handleCenterLayerHorizontally(selectedLayer.id)} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={{ backgroundColor: "#242429", paddingHorizontal: 10, paddingVertical: 5, borderRadius: 14, flexDirection: "row", alignItems: "center", gap: 4, }} > Ortala {/* Çoğalt */} handleDuplicateLayer(selectedLayer.id)} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={{ backgroundColor: "#242429", paddingHorizontal: 8, paddingVertical: 5, borderRadius: 14, }} > {/* Sil */} handleDeleteLayer(selectedLayer.id)} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={{ backgroundColor: "rgba(239, 68, 68, 0.15)", paddingHorizontal: 8, paddingVertical: 5, borderRadius: 14, }} > {/* Hızlı Metin Değiştirme Girdisi */} { onLayersChange((prev) => prev.map((l) => (l.id === selectedLayer.id ? { ...l, text: newText } : l)) ); }} placeholder="Metin yazın..." placeholderTextColor="#71717A" style={{ flex: 1, color: "#FFFFFF", fontSize: 13, fontWeight: "700", padding: 0, }} /> {/* Alt Sıra 1: Font Stili Seçici */} FONT: {CANVAS_FONT_OPTIONS.map((f) => { const isActive = (selectedLayer.fontId || "sans") === f.id; return ( { try { Haptics.selectionAsync(); } catch {} onLayersChange((prev) => prev.map((l) => (l.id === selectedLayer.id ? { ...l, fontId: f.id } : l)) ); }} style={{ paddingHorizontal: 10, paddingVertical: 4, borderRadius: 10, backgroundColor: isActive ? "#D4AF37" : "#242429", }} > {f.label} ); })} {/* Alt Sıra 2: Hızlı Döndürme Açısı Hapları */} EĞİM: {[-15, -6, 0, 6, 15].map((deg) => { const isActive = (selectedLayer.rotation || 0) === deg; return ( { try { Haptics.selectionAsync(); } catch {} onLayersChange((prev) => prev.map((l) => (l.id === selectedLayer.id ? { ...l, rotation: deg } : l)) ); }} style={{ paddingHorizontal: 10, paddingVertical: 4, borderRadius: 10, backgroundColor: isActive ? "#D4AF37" : "#242429", }} > {deg}° ); })} {/* Alt Sıra 3: Kavis (Eğri Yazı) Stepper & Preset Hapları */} KAVİS: {/* Stepper Butonları (- / +) */} { try { Haptics.selectionAsync(); } catch {} onLayersChange((prev) => prev.map((l) => l.id === selectedLayer.id ? { ...l, curveDegree: Math.max(-100, (l.curveDegree || 0) - 5) } : l ) ); }} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: "rgba(255,255,255,0.12)", alignItems: "center", justifyContent: "center" }} > {(selectedLayer.curveDegree || 0)}° { try { Haptics.selectionAsync(); } catch {} onLayersChange((prev) => prev.map((l) => l.id === selectedLayer.id ? { ...l, curveDegree: Math.min(100, (l.curveDegree || 0) + 5) } : l ) ); }} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: "rgba(255,255,255,0.12)", alignItems: "center", justifyContent: "center" }} > {/* Hazır Kavis Presetleri */} {[ { label: "0° (Düz)", deg: 0 }, { label: "20° ◠", deg: 20 }, { label: "35° ◠", deg: 35 }, { label: "50° ◠", deg: 50 }, { label: "70° ◠", deg: 70 }, { label: "-35° ◡", deg: -35 }, { label: "-50° ◡", deg: -50 }, ].map((item) => { const isActive = (selectedLayer.curveDegree || 0) === item.deg; return ( { try { Haptics.selectionAsync(); } catch {} onLayersChange((prev) => prev.map((l) => (l.id === selectedLayer.id ? { ...l, curveDegree: item.deg } : l)) ); }} style={{ paddingHorizontal: 9, paddingVertical: 4, borderRadius: 10, backgroundColor: isActive ? "#D4AF37" : "#242429", }} > {item.label} ); })} )} {/* 3B: SEÇİLİ QR KOD ARAÇLARI (TAM GELİŞMİŞ AYARLAR) */} {isQrSelected && ( {/* Üst Başlık & Ortala / Kapat */} QR KOD AYARLARI ({ backgroundColor: "#242429", paddingHorizontal: 11, paddingVertical: 5, borderRadius: 14, borderWidth: 1, borderColor: "rgba(212, 175, 55, 0.3)", flexDirection: "row", alignItems: "center", gap: 5, opacity: pressed ? 0.8 : 1, })} > Ortala onSelectQr(false)} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={{ backgroundColor: "rgba(255,255,255,0.08)", paddingHorizontal: 9, paddingVertical: 5, borderRadius: 14, }} > Kapat {/* 1. Hassas Boyut Stepper'ı & Hızlı Seçenekler */} BOYUT: { try { Haptics.selectionAsync(); } catch {} onQrConfigChange((prev) => ({ ...prev, size: Math.max(70, prev.size - 5) })); }} style={{ backgroundColor: "#27272A", width: 28, height: 24, borderRadius: 8, alignItems: "center", justifyContent: "center", }} > - {qrConfig.size} px { try { Haptics.selectionAsync(); } catch {} onQrConfigChange((prev) => ({ ...prev, size: Math.min(220, prev.size + 5) })); }} style={{ backgroundColor: "#27272A", width: 28, height: 24, borderRadius: 8, alignItems: "center", justifyContent: "center", }} > + {[ { label: "Kompakt", size: 95 }, { label: "Standart", size: 115 }, { label: "Büyük", size: 135 }, { label: "Geniş", size: 155 }, ].map((p) => { const isActive = Math.abs(qrConfig.size - p.size) <= 5; return ( { try { Haptics.selectionAsync(); } catch {} onQrConfigChange((prev) => ({ ...prev, size: p.size })); }} style={{ flex: 1, backgroundColor: isActive ? "#D4AF37" : "#242429", paddingVertical: 5, borderRadius: 8, alignItems: "center", }} > {p.label} ); })} {/* 2. Kenarlık Rengi (Border Color) */} ÇERÇEVE KENARLIK RENGİ: {[ { color: "#D4AF37", name: "Altın" }, { color: "#F6EFE2", name: "Krem" }, { color: "#FFFFFF", name: "Beyaz" }, { color: "#18181B", name: "Siyah" }, { color: "#8C6239", name: "Bronz" }, { color: "#0C4443", name: "Zümrüt" }, { color: "#B92B27", name: "Kırmızı" }, { color: "transparent", name: "Yok" }, ].map((item) => { const isSelected = (qrConfig.borderColor || "#D4AF37").toLowerCase() === item.color.toLowerCase(); return ( { try { Haptics.selectionAsync(); } catch {} onQrConfigChange((prev) => ({ ...prev, borderColor: item.color })); }} style={{ flexDirection: "row", alignItems: "center", gap: 5, paddingHorizontal: 8, paddingVertical: 4, borderRadius: 10, backgroundColor: isSelected ? "rgba(212, 175, 55, 0.25)" : "#242429", borderWidth: 1.5, borderColor: isSelected ? "#D4AF37" : "transparent", }} > {item.name} ); })} {/* 3. Köşe Yuvarlaklığı & Kenarlık Kalınlığı */} {/* Köşe */} KÖŞE OVAL: {[ { label: "Düz", radius: 0 }, { label: "Hafif", radius: 8 }, { label: "Zarif", radius: 14 }, { label: "Yuvarlak", radius: 24 }, ].map((r) => { const isActive = qrConfig.borderRadius === r.radius; return ( { try { Haptics.selectionAsync(); } catch {} onQrConfigChange((prev) => ({ ...prev, borderRadius: r.radius })); }} style={{ flex: 1, backgroundColor: isActive ? "#D4AF37" : "#242429", paddingVertical: 5, borderRadius: 7, alignItems: "center", }} > {r.label} ); })} {/* Kalınlık */} KALINLIK: {[ { label: "0px", width: 0 }, { label: "1px", width: 1 }, { label: "2px", width: 2 }, { label: "3px", width: 3 }, ].map((w) => { const isActive = (qrConfig.borderWidth ?? 2) === w.width; return ( { try { Haptics.selectionAsync(); } catch {} onQrConfigChange((prev) => ({ ...prev, borderWidth: w.width })); }} style={{ flex: 1, backgroundColor: isActive ? "#D4AF37" : "#242429", paddingVertical: 5, borderRadius: 7, alignItems: "center", }} > {w.label} ); })} )} {/* 4. INSTAGRAM METİN DÜZENLEME MODALI */} { setIsTextModalOpen(false); setEditingLayer(null); }} onDelete={handleDeleteLayer} /> ); };