import type { FC } from '../../../lib/teact/teact'; import { memo, useEffect, useMemo, useRef } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiChat, ApiSticker, ApiStickerSet } from '../../../api/types'; import type { StickerSetOrReactionsSetOrRecent, ThreadId } from '../../../types'; import { CHAT_STICKER_SET_ID, EFFECT_EMOJIS_SET_ID, EFFECT_STICKERS_SET_ID, FAVORITE_SYMBOL_SET_ID, RECENT_SYMBOL_SET_ID, SLIDE_TRANSITION_DURATION, STICKER_PICKER_MAX_SHARED_COVERS, STICKER_SIZE_PICKER_HEADER, } from '../../../config'; import { selectChat, selectChatFullInfo, selectIsChatWithSelf, selectIsCurrentUserPremium, selectShouldLoopStickers, } from '../../../global/selectors'; import animateHorizontalScroll from '../../../util/animateHorizontalScroll'; import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; import { isUserId } from '../../../util/entities/ids'; import { pickTruthy } from '../../../util/iteratees'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { REM } from '../../common/helpers/mediaDimensions'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; import useScrolledState from '../../../hooks/useScrolledState'; import useSendMessageAction from '../../../hooks/useSendMessageAction'; import { useStickerPickerObservers } from '../../common/hooks/useStickerPickerObservers'; import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import Avatar from '../../common/Avatar'; import Icon from '../../common/icons/Icon'; import StickerButton from '../../common/StickerButton'; import StickerSet from '../../common/StickerSet'; import Button from '../../ui/Button'; import Loading from '../../ui/Loading'; import Transition from '../../ui/Transition.tsx'; import StickerSetCover from './StickerSetCover'; import styles from './StickerPicker.module.scss'; type OwnProps = { chatId: string; threadId?: ThreadId; className: string; isHidden?: boolean; isTranslucent?: boolean; loadAndPlay: boolean; canSendStickers?: boolean; noContextMenus?: boolean; idPrefix: string; onStickerSelect: ( sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, canUpdateStickerSetsOrder?: boolean, ) => void; isForEffects?: boolean; }; type StateProps = { chat?: ApiChat; recentStickers: ApiSticker[]; favoriteStickers: ApiSticker[]; effectStickers?: ApiSticker[]; effectEmojis?: ApiSticker[]; stickerSetsById: Record; chatStickerSetId?: string; addedSetIds?: string[]; canAnimate?: boolean; isSavedMessages?: boolean; isCurrentUserPremium?: boolean; }; const HEADER_BUTTON_WIDTH = 2.5 * REM; // px (including margin) const StickerPicker: FC = ({ chat, threadId, className, isHidden, isTranslucent, loadAndPlay, canSendStickers, recentStickers, favoriteStickers, effectStickers, effectEmojis, addedSetIds, stickerSetsById, chatStickerSetId, canAnimate, isSavedMessages, isCurrentUserPremium, noContextMenus, idPrefix, onStickerSelect, isForEffects, }) => { const { loadRecentStickers, addRecentSticker, unfaveSticker, faveSticker, removeRecentSticker, } = getActions(); const containerRef = useRef(); const headerRef = useRef(); const sharedCanvasRef = useRef(); const { handleScroll: handleContentScroll, isAtBeginning: shouldHideTopBorder, } = useScrolledState(); const sendMessageAction = useSendMessageAction(chat?.id, threadId); const prefix = `${idPrefix}-sticker-set`; const { activeSetIndex, observeIntersectionForSet, observeIntersectionForPlayingItems, observeIntersectionForShowingItems, observeIntersectionForCovers, selectStickerSet, } = useStickerPickerObservers(containerRef, headerRef, prefix, isHidden); const lang = useOldLang(); const areAddedLoaded = Boolean(addedSetIds); const allSets = useMemo(() => { if (isForEffects && effectStickers) { const effectSets: StickerSetOrReactionsSetOrRecent[] = []; if (effectEmojis?.length) { effectSets.push({ id: EFFECT_EMOJIS_SET_ID, accessHash: '0', title: '', stickers: effectEmojis, count: effectEmojis.length, isEmoji: true, }); } if (effectStickers?.length) { effectSets.push({ id: EFFECT_STICKERS_SET_ID, accessHash: '0', title: lang('StickerEffects'), stickers: effectStickers, count: effectStickers.length, }); } return effectSets; } if (!addedSetIds) { return MEMO_EMPTY_ARRAY; } const defaultSets = []; if (favoriteStickers.length) { defaultSets.push({ id: FAVORITE_SYMBOL_SET_ID, accessHash: '0', title: lang('FavoriteStickers'), stickers: favoriteStickers, count: favoriteStickers.length, }); } if (recentStickers.length) { defaultSets.push({ id: RECENT_SYMBOL_SET_ID, accessHash: '0', title: lang('RecentStickers'), stickers: recentStickers, count: recentStickers.length, }); } const userSetIds = [...(addedSetIds || [])]; if (chatStickerSetId) { userSetIds.unshift(chatStickerSetId); } const existingAddedSetIds = Object.values(pickTruthy(stickerSetsById, userSetIds)); return [ ...defaultSets, ...existingAddedSetIds, ]; }, [ addedSetIds, stickerSetsById, favoriteStickers, recentStickers, chatStickerSetId, lang, effectStickers, isForEffects, effectEmojis, ]); const noPopulatedSets = useMemo(() => ( areAddedLoaded && allSets.filter((set) => set.stickers?.length).length === 0 ), [allSets, areAddedLoaded]); useEffect(() => { if (!loadAndPlay) return; loadRecentStickers(); if (!canSendStickers) return; sendMessageAction({ type: 'chooseSticker' }); }, [canSendStickers, loadAndPlay, loadRecentStickers, sendMessageAction]); const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION); const shouldRenderContents = areAddedLoaded && canRenderContents && !noPopulatedSets && (canSendStickers || isForEffects); useHorizontalScroll(headerRef, !shouldRenderContents || !headerRef.current); // Scroll container and header when active set changes useEffect(() => { if (!areAddedLoaded) { return; } const header = headerRef.current; if (!header) { return; } const newLeft = activeSetIndex * HEADER_BUTTON_WIDTH - (header.offsetWidth / 2 - HEADER_BUTTON_WIDTH / 2); animateHorizontalScroll(header, newLeft); }, [areAddedLoaded, activeSetIndex]); const handleStickerSelect = useLastCallback((sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => { onStickerSelect(sticker, isSilent, shouldSchedule, true); addRecentSticker({ sticker }); }); const handleStickerUnfave = useLastCallback((sticker: ApiSticker) => { unfaveSticker({ sticker }); }); const handleStickerFave = useLastCallback((sticker: ApiSticker) => { faveSticker({ sticker }); }); const handleMouseMove = useLastCallback(() => { if (!canSendStickers) return; sendMessageAction({ type: 'chooseSticker' }); }); const handleRemoveRecentSticker = useLastCallback((sticker: ApiSticker) => { removeRecentSticker({ sticker }); }); if (!chat) return undefined; function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) { const firstSticker = stickerSet.stickers?.[0]; const buttonClassName = buildClassName(styles.stickerCover, index === activeSetIndex && styles.activated); const withSharedCanvas = index < STICKER_PICKER_MAX_SHARED_COVERS; if (stickerSet.id === RECENT_SYMBOL_SET_ID || stickerSet.id === FAVORITE_SYMBOL_SET_ID || stickerSet.id === CHAT_STICKER_SET_ID || stickerSet.hasThumbnail || !firstSticker ) { return ( ); } else { return ( ); } } const fullClassName = buildClassName(styles.root, className); const headerClassName = buildClassName( styles.header, 'no-scrollbar', !shouldHideTopBorder && styles.headerWithBorder, ); const isLoading = !shouldRenderContents && (canSendStickers || isForEffects) && !noPopulatedSets; return ( {!shouldRenderContents ? ( !canSendStickers && !isForEffects ? (
{lang('ErrorSendRestrictedStickersAll')}
) : noPopulatedSets ? (
{lang('NoStickers')}
) : ( ) ) : ( <> {!isForEffects && (
{allSets.map(renderCover)}
)}
{allSets.map((stickerSet, i) => ( = i - 1 && activeSetIndex <= i + 1} favoriteStickers={favoriteStickers} isSavedMessages={isSavedMessages} isCurrentUserPremium={isCurrentUserPremium} isTranslucent={isTranslucent} isChatStickerSet={stickerSet.id === chatStickerSetId} onStickerSelect={handleStickerSelect} onStickerUnfave={handleStickerUnfave} onStickerFave={handleStickerFave} onStickerRemoveRecent={handleRemoveRecentSticker} forcePlayback shouldHideHeader={stickerSet.id === EFFECT_EMOJIS_SET_ID} /> ))}
)}
); }; export default memo(withGlobal( (global, { chatId }): StateProps => { const { setsById, added, recent, favorite, effect, } = global.stickers; const isSavedMessages = selectIsChatWithSelf(global, chatId); const chat = selectChat(global, chatId); const chatStickerSetId = !isUserId(chatId) ? selectChatFullInfo(global, chatId)?.stickerSet?.id : undefined; return { chat, effectStickers: effect?.stickers, effectEmojis: effect?.emojis, recentStickers: recent.stickers, favoriteStickers: favorite.stickers, stickerSetsById: setsById, addedSetIds: added.setIds, canAnimate: selectShouldLoopStickers(global), isSavedMessages, isCurrentUserPremium: selectIsCurrentUserPremium(global), chatStickerSetId, }; }, )(StickerPicker));