import { emojis } from '@components/Emojis' import Icon from '@components/Icon' import CustomText from '@components/Text' import { useAppDispatch } from '@root/store' import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { countInstanceEmoji } from '@utils/slices/instancesSlice' import { StyleConstants } from '@utils/styles/constants' import layoutAnimation from '@utils/styles/layoutAnimation' import { useTheme } from '@utils/styles/ThemeManager' import { chunk } from 'lodash' import React, { useContext, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { AccessibilityInfo, findNodeHandle, Pressable, SectionList, TextInput, View } from 'react-native' import FastImage from 'react-native-fast-image' import validUrl from 'valid-url' import EmojisContext from './helpers/EmojisContext' const EmojisList = () => { const dispatch = useAppDispatch() const { reduceMotionEnabled } = useAccessibility() const { t } = useTranslation() const { emojisState, emojisDispatch } = useContext(EmojisContext) const { colors, mode } = useTheme() const addEmoji = (shortcode: string) => { if (emojisState.targetIndex === -1) { return } const { value: [value, setValue], selection: [selection, setSelection], ref, maxLength } = emojisState.inputProps[emojisState.targetIndex] const contentFront = value.slice(0, selection.start) const contentRear = value.slice(selection.end || selection.start) const spaceFront = value.length === 0 || /\s/g.test(contentFront.slice(-1)) ? '' : ' ' const spaceRear = /\s/g.test(contentRear[0]) ? '' : ' ' setValue( [contentFront, spaceFront, shortcode, spaceRear, contentRear].join('').slice(0, maxLength) ) const addedLength = spaceFront.length + shortcode.length + spaceRear.length setSelection({ start: selection.start + addedLength }) ref?.current?.setNativeProps({ selection: { start: selection.start + addedLength } }) } const listItem = ({ index, item }: { item: Mastodon.Emoji[]; index: number }) => { return ( {item.map(emoji => { const uri = reduceMotionEnabled ? emoji.static_url : emoji.url if (validUrl.isHttpsUri(uri)) { return ( { addEmoji(`:${emoji.shortcode}:`) dispatch(countInstanceEmoji(emoji)) }} style={{ padding: StyleConstants.Spacing.S }} > ) } else { return null } })} ) } const listRef = useRef(null) useEffect(() => { const tagEmojis = findNodeHandle(listRef.current) if (emojisState.targetIndex !== -1) { layoutAnimation() tagEmojis && AccessibilityInfo.setAccessibilityFocus(tagEmojis) } }, [emojisState.targetIndex]) const [search, setSearch] = useState('') const searchLength = useRef(0) useEffect(() => { if ( (search.length === 0 && searchLength.current === 1) || (search.length === 1 && searchLength.current === 0) ) { layoutAnimation() } searchLength.current = search.length }, [search.length, searchLength.current]) return emojisState.targetIndex !== -1 ? ( { if (emojisState.targetIndex !== -1) { emojisState.inputProps[emojisState.targetIndex].ref?.current?.focus() } emojisDispatch({ type: 'target', payload: -1 }) }} > e.type !== 'frequent') .flatMap(e => e.data.flatMap(e => e).filter(emoji => emoji.shortcode.includes(search)) ), 2 ) : [] } ] : emojis.current || [] } keyExtractor={item => item[0]?.shortcode} renderSectionHeader={({ section: { title } }) => ( {title} )} renderItem={listItem} windowSize={4} contentContainerStyle={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding, minHeight: 32 * 2 + StyleConstants.Spacing.M * 3 }} /> ) : null } export default EmojisList