import { useEffect, useRef } from 'react'; import { Animated, Easing, Platform } from 'react-native'; import { useKeyboardHeight } from './useKeyboardHeight'; /** * App-weite Composable für Sheets/Modals mit TextInput. * * Liefert ein **kombiniertes Animated.Value** für `transform: [{ translateY }]`, * das gleichzeitig: * - die Slide-In/Out-Animation des Sheets ausführt (von unten reinkommend) * - das Sheet automatisch über die Tastatur lifted wenn TextInput fokussiert * * Beide Animationen laufen im **native driver** (Performance + smoother als * height-Animationen). Kein Driver-Mix, kein Bouncing. * * Pattern (verifiziert auf EditMailAccountSheet + GameOverScreen): * ```tsx * const sheetH = SCREEN_HEIGHT * 0.5; * const lift = useSheetKeyboardLift({ visible, offscreenY: sheetH }); * * * * {form content with TextInput} * * * ``` * * Anti-Pattern (was schief ging): `height: animatedValue` + `transform: animatedValue` * auf demselben Animated.View → native-animated-module-Crash. Stattdessen feste * height + nur translateY animieren. * * Anti-Pattern 2: `marginBottom: keyboardHeight` als JS-style + native transform * im selben View → Bouncing weil zwei verschiedene Threads layouten. * * Für FlatList-basierte Sheets (PostCommentsSheet) ist das Pattern anders: * dort wächst die Sheet-Höhe selbst weil eine variable Liste drin ist. Diese * Composable ist für FIXED-HEIGHT-Form-Sheets gedacht. */ export interface SheetKeyboardLiftOptions { /** Ob das Sheet aktuell sichtbar ist. Nur dann läuft Slide-In an. */ visible: boolean; /** Y-Offset des Sheets im verborgenen Zustand (typischerweise = SHEET_HEIGHT). */ offscreenY: number; /** Slide-Dauer in ms. Default 280. */ slideDurationMs?: number; } export function useSheetKeyboardLift({ visible, offscreenY, slideDurationMs = 280, }: SheetKeyboardLiftOptions) { const keyboardHeight = useKeyboardHeight(); const slideY = useRef(new Animated.Value(offscreenY)).current; const keyboardLift = useRef(new Animated.Value(0)).current; // Slide-In bei visible-Wechsel useEffect(() => { if (visible) { slideY.setValue(offscreenY); Animated.timing(slideY, { toValue: 0, duration: slideDurationMs, easing: Easing.out(Easing.cubic), useNativeDriver: true, }).start(); } }, [visible, offscreenY, slideDurationMs, slideY]); // Keyboard-Lift (iOS only — Android adjustResize macht das im Manifest) useEffect(() => { if (Platform.OS !== 'ios') return; Animated.timing(keyboardLift, { toValue: keyboardHeight, duration: 220, easing: Easing.out(Easing.cubic), useNativeDriver: true, }).start(); }, [keyboardHeight, keyboardLift]); return { /** Direkt in `transform: [{ translateY }]` einsetzen. */ translateY: Animated.subtract(slideY, keyboardLift), /** Manuelle Slide-Out-Animation (z.B. beim Close-Tap statt nur visible=false). */ slideOut: (cb?: () => void) => Animated.timing(slideY, { toValue: offscreenY, duration: 220, useNativeDriver: true, }).start(() => cb?.()), /** Live keyboard-Höhe für extra Layout-Berechnungen wenn nötig. */ keyboardHeight, }; }