// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {createContext, useContext, useMemo, useRef, useCallback, type ReactNode} from 'react'; import {useSharedValue, type SharedValue} from 'react-native-reanimated'; import type {PasteInputRef} from '@mattermost/react-native-paste-input'; interface KeyboardAnimationContextType { keyboardTranslateY: SharedValue; bottomInset: SharedValue; scrollOffset: SharedValue; keyboardHeight: SharedValue; scrollPosition: SharedValue; onScroll: (event: unknown) => void; postInputContainerHeight: number; inputRef: React.MutableRefObject; blurInput: () => void; focusInput: () => void; blurAndDismissKeyboard: () => Promise; isKeyboardFullyOpen: SharedValue; isKeyboardFullyClosed: SharedValue; isKeyboardInTransition: SharedValue; showInputAccessoryView: boolean; isInputAccessoryViewMode: SharedValue; setShowInputAccessoryView: (show: boolean) => void; lastKeyboardHeight: number; inputAccessoryViewAnimatedHeight: SharedValue; isTransitioningFromCustomView: SharedValue; closeInputAccessoryView: () => void; scrollToEnd: () => void; isEmojiSearchFocused: boolean; setIsEmojiSearchFocused: (focused: boolean) => void; cursorPositionRef: React.MutableRefObject; registerCursorPosition: (cursorPosition: number, valueLength?: number) => void; preserveCursorPositionForEmojiPicker: () => void; clearCursorPositionPreservation: () => void; isInEmojiPickerTransition: () => boolean; getPreservedCursorPosition: () => number | null; updateValue: React.Dispatch> | null; updateCursorPosition: React.Dispatch> | null; registerPostInputCallbacks: ( updateValueFn: React.Dispatch>, updateCursorPositionFn: React.Dispatch> ) => void; } const KeyboardAnimationContext = createContext(null); export const KeyboardAnimationProvider = ({ children, value, }: { children: ReactNode; value: KeyboardAnimationContextType; }) => { return ( {children} ); }; const DEFAULT_POST_INPUT_HEIGHT = 91; export const useKeyboardAnimationContext = () => { const context = useContext(KeyboardAnimationContext); // Always create default values (hooks must be called unconditionally) const defaultKeyboardTranslateY = useSharedValue(0); const defaultBottomInset = useSharedValue(0); const defaultScrollOffset = useSharedValue(0); const defaultKeyboardHeight = useSharedValue(0); const defaultScrollPosition = useSharedValue(0); const defaultIsKeyboardFullyOpen = useSharedValue(false); const defaultIsKeyboardFullyClosed = useSharedValue(true); const defaultIsKeyboardInTransition = useSharedValue(false); const defaultInputRef = useRef(undefined); const defaultIsInputAccessoryViewMode = useSharedValue(false); const defaultIsTransitioningFromCustomView = useSharedValue(false); const defaultInputAccessoryViewAnimatedHeight = useSharedValue(0); const defaultOnScroll = useCallback(() => { // No-op fallback }, []); const defaultBlurInput = useCallback(() => { // No-op fallback }, []); const defaultFocusInput = useCallback(() => { // No-op fallback }, []); const defaultBlurAndDismissKeyboard = useCallback(async () => { // No-op fallback }, []); const defaultSetShowInputAccessoryView = useCallback(() => { // No-op fallback }, []); const defaultCloseInputAccessoryView = useCallback(() => { // No-op fallback }, []); const defaultScrollToEnd = useCallback(() => { // No-op fallback }, []); const defaultSetIsEmojiSearchFocused = useCallback(() => { // No-op fallback }, []); const defaultCursorPositionRef = useRef(0); const defaultRegisterCursorPosition = useCallback(() => { // No-op fallback }, []); const defaultPreserveCursorPositionForEmojiPicker = useCallback(() => { // No-op fallback }, []); const defaultClearCursorPositionPreservation = useCallback(() => { // No-op fallback }, []); const defaultIsInEmojiPickerTransition = useCallback(() => false, []); const defaultGetPreservedCursorPosition = useCallback(() => null, []); const defaultUpdateValue = useRef> | null>(null); const defaultUpdateCursorPosition = useRef> | null>(null); const defaultRegisterPostInputCallbacks = useCallback(() => { // No-op fallback }, []); const fallbackValue = useMemo(() => ({ keyboardTranslateY: defaultKeyboardTranslateY, bottomInset: defaultBottomInset, scrollOffset: defaultScrollOffset, keyboardHeight: defaultKeyboardHeight, scrollPosition: defaultScrollPosition, onScroll: defaultOnScroll, postInputContainerHeight: DEFAULT_POST_INPUT_HEIGHT, inputRef: defaultInputRef, blurInput: defaultBlurInput, focusInput: defaultFocusInput, blurAndDismissKeyboard: defaultBlurAndDismissKeyboard, isKeyboardFullyOpen: defaultIsKeyboardFullyOpen, isKeyboardFullyClosed: defaultIsKeyboardFullyClosed, isKeyboardInTransition: defaultIsKeyboardInTransition, setShowInputAccessoryView: defaultSetShowInputAccessoryView, showInputAccessoryView: false, lastKeyboardHeight: 0, isInputAccessoryViewMode: defaultIsInputAccessoryViewMode, inputAccessoryViewAnimatedHeight: defaultInputAccessoryViewAnimatedHeight, isTransitioningFromCustomView: defaultIsTransitioningFromCustomView, closeInputAccessoryView: defaultCloseInputAccessoryView, scrollToEnd: defaultScrollToEnd, isEmojiSearchFocused: false, setIsEmojiSearchFocused: defaultSetIsEmojiSearchFocused, cursorPositionRef: defaultCursorPositionRef, registerCursorPosition: defaultRegisterCursorPosition, preserveCursorPositionForEmojiPicker: defaultPreserveCursorPositionForEmojiPicker, clearCursorPositionPreservation: defaultClearCursorPositionPreservation, isInEmojiPickerTransition: defaultIsInEmojiPickerTransition, getPreservedCursorPosition: defaultGetPreservedCursorPosition, updateValue: defaultUpdateValue.current, updateCursorPosition: defaultUpdateCursorPosition.current, registerPostInputCallbacks: defaultRegisterPostInputCallbacks, }), [ defaultKeyboardTranslateY, defaultBottomInset, defaultScrollOffset, defaultKeyboardHeight, defaultScrollPosition, defaultOnScroll, defaultBlurInput, defaultFocusInput, defaultBlurAndDismissKeyboard, defaultIsKeyboardFullyOpen, defaultIsKeyboardFullyClosed, defaultIsKeyboardInTransition, defaultSetShowInputAccessoryView, defaultIsInputAccessoryViewMode, defaultInputAccessoryViewAnimatedHeight, defaultIsTransitioningFromCustomView, defaultCloseInputAccessoryView, defaultScrollToEnd, defaultSetIsEmojiSearchFocused, defaultRegisterCursorPosition, defaultPreserveCursorPositionForEmojiPicker, defaultClearCursorPositionPreservation, defaultIsInEmojiPickerTransition, defaultGetPreservedCursorPosition, defaultUpdateValue, defaultUpdateCursorPosition, defaultRegisterPostInputCallbacks, ]); // If context exists, return it; otherwise return fallback return context || fallbackValue; };