Launch
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
Animated,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useCoachMarks } from '../context/CoachMarksContext';
|
||||
@@ -19,14 +18,13 @@ const TOOLTIP_VERTICAL_OFFSET = 32;
|
||||
|
||||
export const CoachMarksOverlay: React.FC = () => {
|
||||
const { isActive, currentStep, steps, layouts, next, skip } = useCoachMarks();
|
||||
const { isDarkMode, colorPalette } = useApp();
|
||||
const { isDarkMode, colorPalette, t } = 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);
|
||||
@@ -36,24 +34,22 @@ export const CoachMarksOverlay: React.FC = () => {
|
||||
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]);
|
||||
}, [currentStep, fadeAnim, isActive, pulseAnim, scaleAnim]);
|
||||
|
||||
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,
|
||||
@@ -64,10 +60,13 @@ export const CoachMarksOverlay: React.FC = () => {
|
||||
}
|
||||
: { 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));
|
||||
const 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;
|
||||
@@ -80,39 +79,43 @@ export const CoachMarksOverlay: React.FC = () => {
|
||||
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));
|
||||
tooltipY = Math.max(24, Math.min(SCREEN_H - tooltipMaxH - 24, tooltipY));
|
||||
|
||||
const arrowPointsUp = tooltipY > highlight.y;
|
||||
|
||||
return (
|
||||
<Animated.View style={[StyleSheet.absoluteFill, styles.root, { opacity: fadeAnim }]} pointerEvents="box-none">
|
||||
{/* Dark overlay — 4 Rechtecke um die Aussparung */}
|
||||
{/* Oben */}
|
||||
<View style={[styles.overlay, { top: 0, left: 0, right: 0, height: Math.max(0, highlight.y) }]} />
|
||||
{/* Links */}
|
||||
<View style={[styles.overlay, {
|
||||
top: highlight.y, left: 0, width: Math.max(0, highlight.x), height: highlight.h,
|
||||
}]} />
|
||||
{/* Rechts */}
|
||||
<View style={[styles.overlay, {
|
||||
top: highlight.y,
|
||||
left: highlight.x + highlight.w,
|
||||
right: 0,
|
||||
height: highlight.h,
|
||||
}]} />
|
||||
{/* Unten */}
|
||||
<View style={[styles.overlay, {
|
||||
top: highlight.y + highlight.h,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}]} />
|
||||
<View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{ top: highlight.y, left: 0, width: Math.max(0, highlight.x), height: highlight.h },
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{
|
||||
top: highlight.y,
|
||||
left: highlight.x + highlight.w,
|
||||
right: 0,
|
||||
height: highlight.h,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{
|
||||
top: highlight.y + highlight.h,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Pulsierender Ring um das Highlight */}
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.highlightRing,
|
||||
@@ -129,7 +132,6 @@ export const CoachMarksOverlay: React.FC = () => {
|
||||
pointerEvents="none"
|
||||
/>
|
||||
|
||||
{/* Tooltip-Karte */}
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tooltip,
|
||||
@@ -144,7 +146,6 @@ export const CoachMarksOverlay: React.FC = () => {
|
||||
},
|
||||
]}
|
||||
>
|
||||
{/* Arrow */}
|
||||
<View
|
||||
style={[
|
||||
styles.arrow,
|
||||
@@ -157,7 +158,6 @@ export const CoachMarksOverlay: React.FC = () => {
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Schritt-Indikator */}
|
||||
<View style={styles.stepRow}>
|
||||
{steps.map((_, i) => (
|
||||
<View
|
||||
@@ -176,7 +176,7 @@ export const CoachMarksOverlay: React.FC = () => {
|
||||
|
||||
<View style={styles.tooltipFooter}>
|
||||
<TouchableOpacity onPress={skip} style={styles.skipBtn}>
|
||||
<Text style={[styles.skipText, { color: colors.textMuted }]}>Überspringen</Text>
|
||||
<Text style={[styles.skipText, { color: colors.textMuted }]}>{t.coachSkip}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={next}
|
||||
@@ -184,7 +184,7 @@ export const CoachMarksOverlay: React.FC = () => {
|
||||
activeOpacity={0.82}
|
||||
>
|
||||
<Text style={[styles.nextText, { color: colors.onPrimary }]}>
|
||||
{currentStep === steps.length - 1 ? 'Fertig' : 'Weiter'}
|
||||
{currentStep === steps.length - 1 ? t.coachDone : t.coachNext}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name={currentStep === steps.length - 1 ? 'checkmark' : 'arrow-forward'}
|
||||
|
||||
Reference in New Issue
Block a user