// Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { createContext, memo, useCallback, useContext, useMemo, useState, } from 'react'; import { strictAssert } from '../../util/assert'; import type { LocalizerType } from '../../types/I18N'; import type { StickerPackType, StickerType } from '../../state/ducks/stickers'; import type { EmojiSkinTone } from './data/emojis'; import { EmojiPickerCategory, type EmojiParentKey } from './data/emojis'; import type { GifType } from './panels/FunPanelGifs'; import type { FunGifsSection, FunStickersSection } from './FunConstants'; import { type FunEmojisSection, FunGifsCategory, FunPickerTabKey, FunSectionCommon, FunStickersSectionBase, toFunStickersPackSection, } from './FunConstants'; export type FunContextSmartProps = Readonly<{ i18n: LocalizerType; // Recents recentEmojis: ReadonlyArray; recentStickers: ReadonlyArray; recentGifs: ReadonlyArray; // Emojis defaultEmojiSkinTone: EmojiSkinTone; onChangeDefaultEmojiSkinTone: (emojiSkinTone: EmojiSkinTone) => void; // Stickers installedStickerPacks: ReadonlyArray; showStickerPickerHint: boolean; onClearStickerPickerHint: () => unknown; }>; export type FunContextProps = FunContextSmartProps & Readonly<{ // Current Tab tab: FunPickerTabKey; onChangeTab: (key: FunPickerTabKey) => unknown; // Search searchInput: string; onSearchInputChange: (nextSearchInput: string) => void; // Current Section selectedEmojisSection: FunEmojisSection; selectedStickersSection: FunStickersSection; selectedGifsSection: FunGifsSection; onChangeSelectedEmojisSection: (section: FunEmojisSection) => void; onChangeSelectedStickersSection: (section: FunStickersSection) => void; onChangeSelectedSelectGifsSection: (section: FunGifsSection) => void; }>; export const FunContext = createContext(null); export function useFunContext(): FunContextProps { const fun = useContext(FunContext); strictAssert(fun != null, 'Must be wrapped with '); return fun; } type FunProviderInnerProps = FunContextProps & { children: React.ReactNode; }; const FunProviderInner = memo(function FunProviderInner( props: FunProviderInnerProps ): JSX.Element { return ( {props.children} ); }); export type FunProviderProps = FunContextSmartProps & { children: React.ReactNode; }; export const FunProvider = memo(function FunProvider( props: FunProviderProps ): JSX.Element { // Current Tab const [tab, setTab] = useState(FunPickerTabKey.Emoji); const handleChangeTab = useCallback((key: FunPickerTabKey) => { setTab(key); }, []); // Search Input const [searchInput, setSearchInput] = useState(''); const searchQuery = useMemo(() => searchInput.trim(), [searchInput]); const handleSearchInputChange = useCallback((newSearchInput: string) => { setSearchInput(newSearchInput); }, []); // Selected Sections const [selectedEmojisSection, setSelectedEmojisSection] = useState( (): FunEmojisSection => { if (searchQuery !== '') { return FunSectionCommon.SearchResults; } if (props.recentEmojis.length) { return FunSectionCommon.Recents; } return EmojiPickerCategory.SmileysAndPeople; } ); const [selectedStickersSection, setSelectedStickersSection] = useState( (): FunStickersSection => { if (searchQuery !== '') { return FunSectionCommon.SearchResults; } if (props.recentStickers.length > 0) { return FunSectionCommon.Recents; } const firstInstalledStickerPack = props.installedStickerPacks.at(0); if (firstInstalledStickerPack != null) { return toFunStickersPackSection(firstInstalledStickerPack); } return FunStickersSectionBase.StickersSetup; } ); const [selectedGifsSection, setSelectedGifsSection] = useState( (): FunGifsSection => { if (searchQuery !== '') { return FunSectionCommon.SearchResults; } if (props.recentGifs.length > 0) { return FunSectionCommon.Recents; } return FunGifsCategory.Trending; } ); const handleChangeSelectedEmojisSection = useCallback( (section: FunEmojisSection) => { setSelectedEmojisSection(section); }, [] ); const handleChangeSelectedStickersSection = useCallback( (section: FunStickersSection) => { setSelectedStickersSection(section); }, [] ); const handleChangeSelectedGifsSection = useCallback( (section: FunGifsSection) => { setSelectedGifsSection(section); }, [] ); return ( {props.children} ); });