import React, { useEffect, useRef } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, Dimensions, Animated, Platform, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useCoachMarks } from '../context/CoachMarksContext'; import { useApp } from '../context/AppContext'; import { useColors } from '../constants/Colors'; const { width: SCREEN_W, height: SCREEN_H } = Dimensions.get('window'); const HIGHLIGHT_PADDING = 10; const TOOLTIP_VERTICAL_OFFSET = 32; export const CoachMarksOverlay: React.FC = () => { const { isActive, currentStep, steps, layouts, next, skip } = useCoachMarks(); const { isDarkMode, colorPalette } = useApp(); const colors = useColors(isDarkMode, colorPalette); const fadeAnim = useRef(new Animated.Value(0)).current; const scaleAnim = useRef(new Animated.Value(0.88)).current; const pulseAnim = useRef(new Animated.Value(1)).current; // Fade in when tour starts or step changes useEffect(() => { if (isActive) { fadeAnim.setValue(0); scaleAnim.setValue(0.88); Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration: 320, useNativeDriver: true }), Animated.spring(scaleAnim, { toValue: 1, tension: 80, friction: 9, useNativeDriver: true }), ]).start(); // Pulse animation on highlight Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.06, duration: 900, useNativeDriver: true }), Animated.timing(pulseAnim, { toValue: 1, duration: 900, useNativeDriver: true }), ]) ).start(); } else { pulseAnim.stopAnimation(); } }, [isActive, currentStep]); if (!isActive || steps.length === 0) return null; const step = steps[currentStep]; const layout = layouts[step.elementKey]; // Fallback wenn Element noch nicht gemessen const highlight = layout ? { x: layout.x - HIGHLIGHT_PADDING, y: layout.y - HIGHLIGHT_PADDING, w: layout.width + HIGHLIGHT_PADDING * 2, h: layout.height + HIGHLIGHT_PADDING * 2, r: Math.min(layout.width, layout.height) / 2 + HIGHLIGHT_PADDING, } : { x: SCREEN_W / 2 - 40, y: SCREEN_H / 2 - 40, w: 80, h: 80, r: 40 }; // Tooltip-Position berechnen const tooltipW = 260; const tooltipMaxH = 140; let tooltipX = Math.max(12, Math.min(SCREEN_W - tooltipW - 12, highlight.x + highlight.w / 2 - tooltipW / 2)); let tooltipY: number; const spaceBelow = SCREEN_H - (highlight.y + highlight.h); const spaceAbove = highlight.y; if (step.tooltipSide === 'above' || (step.tooltipSide !== 'below' && spaceAbove > spaceBelow)) { tooltipY = highlight.y - tooltipMaxH - 24; if (tooltipY < 60) tooltipY = highlight.y + highlight.h + 24; } else { tooltipY = highlight.y + highlight.h + 24; if (tooltipY + tooltipMaxH > SCREEN_H - 60) tooltipY = highlight.y - tooltipMaxH - 24; } // Keep coachmark bubbles slightly higher to avoid overlap with bright focus circles. tooltipY -= TOOLTIP_VERTICAL_OFFSET; const minTooltipY = 24; const maxTooltipY = SCREEN_H - tooltipMaxH - 24; tooltipY = Math.max(minTooltipY, Math.min(maxTooltipY, tooltipY)); const arrowPointsUp = tooltipY > highlight.y; return ( {/* Dark overlay — 4 Rechtecke um die Aussparung */} {/* Oben */} {/* Links */} {/* Rechts */} {/* Unten */} {/* Pulsierender Ring um das Highlight */} {/* Tooltip-Karte */} {/* Arrow */} {/* Schritt-Indikator */} {steps.map((_, i) => ( ))} {step.title} {step.description} Überspringen {currentStep === steps.length - 1 ? 'Fertig' : 'Weiter'} ); }; const styles = StyleSheet.create({ root: { zIndex: 9999, elevation: 9999, }, overlay: { position: 'absolute', backgroundColor: 'rgba(0,0,0,0.72)', }, highlightRing: { position: 'absolute', borderWidth: 2.5, }, tooltip: { position: 'absolute', borderRadius: 18, borderWidth: 1, padding: 16, gap: 8, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.2, shadowRadius: 20, elevation: 12, }, stepRow: { flexDirection: 'row', gap: 4, alignItems: 'center', marginBottom: 2, }, stepDot: { width: 6, height: 6, borderRadius: 3, }, tooltipTitle: { fontSize: 15, fontWeight: '700', lineHeight: 20, }, tooltipDesc: { fontSize: 13, lineHeight: 18, }, tooltipFooter: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: 4, }, skipBtn: { padding: 4, }, skipText: { fontSize: 13, }, nextBtn: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 16, paddingVertical: 9, borderRadius: 20, }, nextText: { fontSize: 13, fontWeight: '600', }, arrow: { position: 'absolute', width: 0, height: 0, borderLeftWidth: 8, borderRightWidth: 8, borderLeftColor: 'transparent', borderRightColor: 'transparent', }, arrowUp: { top: -8, borderBottomWidth: 8, }, arrowDown: { bottom: -8, borderTopWidth: 8, }, });