mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	POC compose using the new emoji selector
This commit is contained in:
		| @@ -44,7 +44,7 @@ const EmojisList = () => { | ||||
|     const contentFront = value.slice(0, selection.start) | ||||
|     const contentRear = value.slice(selection.end) | ||||
|  | ||||
|     const spaceFront = /\s/g.test(contentFront.slice(-1)) ? '' : ' ' | ||||
|     const spaceFront = value.length === 0 || /\s/g.test(contentFront.slice(-1)) ? '' : ' ' | ||||
|     const spaceRear = /\s/g.test(contentRear[0]) ? '' : ' ' | ||||
|  | ||||
|     setValue( | ||||
| @@ -52,7 +52,6 @@ const EmojisList = () => { | ||||
|     ) | ||||
|  | ||||
|     const addedLength = spaceFront.length + shortcode.length + spaceRear.length | ||||
|  | ||||
|     setSelection({ start: selection.start + addedLength }) | ||||
|     ref?.current?.setNativeProps({ | ||||
|       selection: { start: selection.start + addedLength } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ type inputProps = { | ||||
|   isFocused: MutableRefObject<boolean> | ||||
|   ref?: RefObject<TextInput> // For controlling focus | ||||
|   maxLength?: number | ||||
|   addFunc?: (add: string) => void // For none default state update | ||||
| } | ||||
|  | ||||
| export type EmojisState = { | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| import analytics from '@components/analytics' | ||||
| import { ComponentEmojis } from '@components/Emojis' | ||||
| import { EmojisState } from '@components/Emojis/helpers/EmojisContext' | ||||
| import { HeaderLeft, HeaderRight } from '@components/Header' | ||||
| import { createNativeStackNavigator } from '@react-navigation/native-stack' | ||||
| import haptics from '@root/components/haptics' | ||||
| @@ -6,10 +8,7 @@ import { useAppDispatch } from '@root/store' | ||||
| import formatText from '@screens/Compose/formatText' | ||||
| import ComposeRoot from '@screens/Compose/Root' | ||||
| import { RootStackScreenProps } from '@utils/navigation/navigators' | ||||
| import { | ||||
|   QueryKeyTimeline, | ||||
|   useTimelineMutation | ||||
| } from '@utils/queryHooks/timeline' | ||||
| import { QueryKeyTimeline, useTimelineMutation } from '@utils/queryHooks/timeline' | ||||
| import { updateStoreReview } from '@utils/slices/contextsSlice' | ||||
| import { | ||||
|   getInstanceAccount, | ||||
| @@ -20,22 +19,9 @@ import { | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import { filter } from 'lodash' | ||||
| import React, { | ||||
|   useCallback, | ||||
|   useEffect, | ||||
|   useMemo, | ||||
|   useReducer, | ||||
|   useState | ||||
| } from 'react' | ||||
| import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { | ||||
|   Alert, | ||||
|   Keyboard, | ||||
|   KeyboardAvoidingView, | ||||
|   Platform, | ||||
|   StyleSheet | ||||
| } from 'react-native' | ||||
| import { SafeAreaView } from 'react-native-safe-area-context' | ||||
| import { Alert, Keyboard, Platform } from 'react-native' | ||||
| import { useQueryClient } from 'react-query' | ||||
| import { useSelector } from 'react-redux' | ||||
| import * as Sentry from 'sentry-expo' | ||||
| @@ -60,12 +46,8 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({ | ||||
|  | ||||
|   const [hasKeyboard, setHasKeyboard] = useState(false) | ||||
|   useEffect(() => { | ||||
|     const keyboardShown = Keyboard.addListener('keyboardWillShow', () => | ||||
|       setHasKeyboard(true) | ||||
|     ) | ||||
|     const keyboardHidden = Keyboard.addListener('keyboardWillHide', () => | ||||
|       setHasKeyboard(false) | ||||
|     ) | ||||
|     const keyboardShown = Keyboard.addListener('keyboardWillShow', () => setHasKeyboard(true)) | ||||
|     const keyboardHidden = Keyboard.addListener('keyboardWillHide', () => setHasKeyboard(false)) | ||||
|  | ||||
|     return () => { | ||||
|       keyboardShown.remove() | ||||
| @@ -89,32 +71,23 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({ | ||||
|         attachments: { | ||||
|           ...composeInitialState.attachments, | ||||
|           sensitive: | ||||
|             localAccount?.preferences && | ||||
|             localAccount?.preferences['posting:default:sensitive'] | ||||
|             localAccount?.preferences && localAccount?.preferences['posting:default:sensitive'] | ||||
|               ? localAccount?.preferences['posting:default:sensitive'] | ||||
|               : false | ||||
|         }, | ||||
|         visibility: | ||||
|           localAccount?.preferences && | ||||
|           localAccount.preferences['posting:default:visibility'] | ||||
|           localAccount?.preferences && localAccount.preferences['posting:default:visibility'] | ||||
|             ? localAccount.preferences['posting:default:visibility'] | ||||
|             : 'public' | ||||
|       } | ||||
|     } | ||||
|   }, []) | ||||
|  | ||||
|   const [composeState, composeDispatch] = useReducer( | ||||
|     composeReducer, | ||||
|     initialReducerState | ||||
|   ) | ||||
|   const [composeState, composeDispatch] = useReducer(composeReducer, initialReducerState) | ||||
|  | ||||
|   const maxTootChars = useSelector( | ||||
|     getInstanceConfigurationStatusMaxChars, | ||||
|     () => true | ||||
|   ) | ||||
|   const maxTootChars = useSelector(getInstanceConfigurationStatusMaxChars, () => true) | ||||
|   const totalTextCount = | ||||
|     (composeState.spoiler.active ? composeState.spoiler.count : 0) + | ||||
|     composeState.text.count | ||||
|     (composeState.spoiler.active ? composeState.spoiler.count : 0) + composeState.text.count | ||||
|  | ||||
|   // If compose state is dirty, then disallow add back drafts | ||||
|   useEffect(() => { | ||||
| @@ -173,8 +146,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({ | ||||
|         }) | ||||
|         break | ||||
|       case 'reply': | ||||
|         const actualStatus = | ||||
|           params.incomingStatus.reblog || params.incomingStatus | ||||
|         const actualStatus = params.incomingStatus.reblog || params.incomingStatus | ||||
|         if (actualStatus.spoiler_text) { | ||||
|           formatText({ | ||||
|             textInput: 'spoiler', | ||||
| @@ -278,16 +250,10 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({ | ||||
|     if (totalTextCount > maxTootChars) { | ||||
|       return true | ||||
|     } | ||||
|     if ( | ||||
|       composeState.attachments.uploads.filter(upload => upload.uploading) | ||||
|         .length > 0 | ||||
|     ) { | ||||
|     if (composeState.attachments.uploads.filter(upload => upload.uploading).length > 0) { | ||||
|       return true | ||||
|     } | ||||
|     if ( | ||||
|       composeState.attachments.uploads.length === 0 && | ||||
|       composeState.text.raw.length === 0 | ||||
|     ) { | ||||
|     if (composeState.attachments.uploads.length === 0 && composeState.text.raw.length === 0) { | ||||
|       return true | ||||
|     } | ||||
|     return false | ||||
| @@ -309,18 +275,12 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({ | ||||
|           composePost(params, composeState) | ||||
|             .then(res => { | ||||
|               haptics('Success') | ||||
|               if ( | ||||
|                 Platform.OS === 'ios' && | ||||
|                 Platform.constants.osVersion === '13.3' | ||||
|               ) { | ||||
|               if (Platform.OS === 'ios' && Platform.constants.osVersion === '13.3') { | ||||
|                 // https://github.com/tooot-app/app/issues/59 | ||||
|               } else { | ||||
|                 dispatch(updateStoreReview(1)) | ||||
|               } | ||||
|               const queryKey: QueryKeyTimeline = [ | ||||
|                 'Timeline', | ||||
|                 { page: 'Following' } | ||||
|               ] | ||||
|               const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Following' }] | ||||
|               queryClient.invalidateQueries(queryKey) | ||||
|  | ||||
|               switch (params?.type) { | ||||
| @@ -392,54 +352,61 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({ | ||||
|     }` | ||||
|   }, [totalTextCount, maxTootChars, composeState.dirty]) | ||||
|  | ||||
|   const inputProps: EmojisState['inputProps'] = [ | ||||
|     { | ||||
|       value: [ | ||||
|         composeState.text.raw, | ||||
|         content => formatText({ textInput: 'text', composeDispatch, content }) | ||||
|       ], | ||||
|       selection: [ | ||||
|         composeState.text.selection, | ||||
|         selection => composeDispatch({ type: 'text', payload: { selection } }) | ||||
|       ], | ||||
|       isFocused: useRef<boolean>(composeState.textInputFocus.current === 'text'), | ||||
|       maxLength: maxTootChars | ||||
|     } | ||||
|   ] | ||||
|  | ||||
|   return ( | ||||
|     <KeyboardAvoidingView | ||||
|       style={styles.base} | ||||
|       behavior={Platform.OS === 'ios' ? 'padding' : undefined} | ||||
|     <ComponentEmojis | ||||
|       inputProps={inputProps} | ||||
|       customButton | ||||
|       customBehavior={Platform.OS === 'ios' ? 'padding' : undefined} | ||||
|       customEdges={hasKeyboard ? ['top'] : ['top', 'bottom']} | ||||
|     > | ||||
|       <SafeAreaView | ||||
|         style={styles.base} | ||||
|         edges={hasKeyboard ? ['top'] : ['top', 'bottom']} | ||||
|       > | ||||
|         <ComposeContext.Provider value={{ composeState, composeDispatch }}> | ||||
|           <Stack.Navigator initialRouteName='Screen-Compose-Root'> | ||||
|             <Stack.Screen | ||||
|               name='Screen-Compose-Root' | ||||
|               component={ComposeRoot} | ||||
|               options={{ | ||||
|                 title: headerContent, | ||||
|                 headerTitleStyle: { | ||||
|                   fontWeight: | ||||
|                     totalTextCount > maxTootChars | ||||
|                       ? StyleConstants.Font.Weight.Bold | ||||
|                       : StyleConstants.Font.Weight.Normal, | ||||
|                   fontSize: StyleConstants.Font.Size.M | ||||
|                 }, | ||||
|                 headerTintColor: | ||||
|                   totalTextCount > maxTootChars ? colors.red : colors.secondary, | ||||
|                 headerLeft, | ||||
|                 headerRight | ||||
|               }} | ||||
|             /> | ||||
|             <Stack.Screen | ||||
|               name='Screen-Compose-DraftsList' | ||||
|               component={ComposeDraftsList} | ||||
|               options={{ headerShown: false, presentation: 'modal' }} | ||||
|             /> | ||||
|             <Stack.Screen | ||||
|               name='Screen-Compose-EditAttachment' | ||||
|               component={ComposeEditAttachment} | ||||
|               options={{ headerShown: false, presentation: 'modal' }} | ||||
|             /> | ||||
|           </Stack.Navigator> | ||||
|         </ComposeContext.Provider> | ||||
|       </SafeAreaView> | ||||
|     </KeyboardAvoidingView> | ||||
|       <ComposeContext.Provider value={{ composeState, composeDispatch }}> | ||||
|         <Stack.Navigator initialRouteName='Screen-Compose-Root'> | ||||
|           <Stack.Screen | ||||
|             name='Screen-Compose-Root' | ||||
|             component={ComposeRoot} | ||||
|             options={{ | ||||
|               title: headerContent, | ||||
|               headerTitleStyle: { | ||||
|                 fontWeight: | ||||
|                   totalTextCount > maxTootChars | ||||
|                     ? StyleConstants.Font.Weight.Bold | ||||
|                     : StyleConstants.Font.Weight.Normal, | ||||
|                 fontSize: StyleConstants.Font.Size.M | ||||
|               }, | ||||
|               headerTintColor: totalTextCount > maxTootChars ? colors.red : colors.secondary, | ||||
|               headerLeft, | ||||
|               headerRight | ||||
|             }} | ||||
|           /> | ||||
|           <Stack.Screen | ||||
|             name='Screen-Compose-DraftsList' | ||||
|             component={ComposeDraftsList} | ||||
|             options={{ headerShown: false, presentation: 'modal' }} | ||||
|           /> | ||||
|           <Stack.Screen | ||||
|             name='Screen-Compose-EditAttachment' | ||||
|             component={ComposeEditAttachment} | ||||
|             options={{ headerShown: false, presentation: 'modal' }} | ||||
|           /> | ||||
|         </Stack.Navigator> | ||||
|       </ComposeContext.Provider> | ||||
|     </ComponentEmojis> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   base: { flex: 1 } | ||||
| }) | ||||
|  | ||||
| export default ScreenCompose | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import { chunk, forEach, groupBy, sortBy } from 'lodash' | ||||
| import React, { useContext, useEffect, useMemo, useRef } from 'react' | ||||
| import { AccessibilityInfo, findNodeHandle, FlatList, StyleSheet, View } from 'react-native' | ||||
| import { AccessibilityInfo, findNodeHandle, FlatList, View } from 'react-native' | ||||
| import { Circle } from 'react-native-animated-spinkit' | ||||
| import ComposeActions from './Root/Actions' | ||||
| import ComposePosting from './Posting' | ||||
| @@ -14,9 +14,7 @@ import ComposeRootHeader from './Root/Header' | ||||
| import ComposeRootSuggestion from './Root/Suggestion' | ||||
| import ComposeContext from './utils/createContext' | ||||
| import ComposeDrafts from './Root/Drafts' | ||||
| import FastImage from 'react-native-fast-image' | ||||
| import { useAccessibility } from '@utils/accessibility/AccessibilityManager' | ||||
| import { ComposeState } from './utils/types' | ||||
| import { useSelector } from 'react-redux' | ||||
| import { | ||||
|   getInstanceConfigurationStatusCharsURL, | ||||
| @@ -24,30 +22,6 @@ import { | ||||
| } from '@utils/slices/instancesSlice' | ||||
| import { useTranslation } from 'react-i18next' | ||||
|  | ||||
| const prefetchEmojis = ( | ||||
|   sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>, | ||||
|   reduceMotionEnabled: boolean | ||||
| ) => { | ||||
|   const prefetches: { uri: string }[] = [] | ||||
|   let requestedIndex = 0 | ||||
|   sortedEmojis.forEach(sorted => { | ||||
|     sorted.data.forEach(emojis => | ||||
|       emojis.forEach(emoji => { | ||||
|         if (requestedIndex > 40) { | ||||
|           return | ||||
|         } | ||||
|         prefetches.push({ | ||||
|           uri: reduceMotionEnabled ? emoji.static_url : emoji.url | ||||
|         }) | ||||
|         requestedIndex++ | ||||
|       }) | ||||
|     ) | ||||
|   }) | ||||
|   try { | ||||
|     FastImage.preload(prefetches) | ||||
|   } catch {} | ||||
| } | ||||
|  | ||||
| export let instanceConfigurationStatusCharsURL = 23 | ||||
|  | ||||
| const ComposeRoot = React.memo( | ||||
| @@ -62,7 +36,6 @@ const ComposeRoot = React.memo( | ||||
|  | ||||
|     const accessibleRefDrafts = useRef(null) | ||||
|     const accessibleRefAttachments = useRef(null) | ||||
|     const accessibleRefEmojis = useRef(null) | ||||
|  | ||||
|     useEffect(() => { | ||||
|       const tagDrafts = findNodeHandle(accessibleRefDrafts.current) | ||||
| @@ -110,18 +83,13 @@ const ComposeRoot = React.memo( | ||||
|             ) | ||||
|           }) | ||||
|         } | ||||
|         composeDispatch({ | ||||
|           type: 'emoji', | ||||
|           payload: { ...composeState.emoji, emojis: sortedEmojis } | ||||
|         }) | ||||
|         prefetchEmojis(sortedEmojis, reduceMotionEnabled) | ||||
|       } | ||||
|     }, [emojisData, reduceMotionEnabled]) | ||||
|  | ||||
|     const listEmpty = useMemo(() => { | ||||
|       if (isFetching) { | ||||
|         return ( | ||||
|           <View key='listEmpty' style={styles.loading}> | ||||
|           <View key='listEmpty' style={{ flex: 1, alignItems: 'center' }}> | ||||
|             <Circle size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} /> | ||||
|           </View> | ||||
|         ) | ||||
| @@ -129,17 +97,12 @@ const ComposeRoot = React.memo( | ||||
|     }, [isFetching]) | ||||
|  | ||||
|     const Footer = useMemo( | ||||
|       () => ( | ||||
|         <ComposeRootFooter | ||||
|           accessibleRefAttachments={accessibleRefAttachments} | ||||
|           accessibleRefEmojis={accessibleRefEmojis} | ||||
|         /> | ||||
|       ), | ||||
|       [accessibleRefAttachments.current, accessibleRefEmojis.current] | ||||
|       () => <ComposeRootFooter accessibleRefAttachments={accessibleRefAttachments} />, | ||||
|       [accessibleRefAttachments.current] | ||||
|     ) | ||||
|  | ||||
|     return ( | ||||
|       <View style={styles.base}> | ||||
|       <View style={{ flex: 1 }}> | ||||
|         <FlatList | ||||
|           renderItem={({ item }) => ( | ||||
|             <ComposeRootSuggestion | ||||
| @@ -166,15 +129,4 @@ const ComposeRoot = React.memo( | ||||
|   () => true | ||||
| ) | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   base: { | ||||
|     flex: 1 | ||||
|   }, | ||||
|   contentView: { flex: 1 }, | ||||
|   loading: { | ||||
|     flex: 1, | ||||
|     alignItems: 'center' | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default ComposeRoot | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| import analytics from '@components/analytics' | ||||
| import EmojisContext from '@components/Emojis/helpers/EmojisContext' | ||||
| import Icon from '@components/Icon' | ||||
| import { useActionSheet } from '@expo/react-native-action-sheet' | ||||
| import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice' | ||||
| import layoutAnimation from '@utils/styles/layoutAnimation' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import React, { useCallback, useContext, useMemo } from 'react' | ||||
| import React, { useContext, useMemo } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { Pressable, StyleSheet, View } from 'react-native' | ||||
| import { Keyboard, Pressable, StyleSheet, View } from 'react-native' | ||||
| import { useSelector } from 'react-redux' | ||||
| import ComposeContext from '../utils/createContext' | ||||
| import chooseAndUploadAttachment from './Footer/addAttachment' | ||||
| @@ -30,22 +31,19 @@ const ComposeActions: React.FC = () => { | ||||
|       return colors.secondary | ||||
|     } | ||||
|   }, [composeState.poll.active, composeState.attachments.uploads]) | ||||
|   const attachmentOnPress = useCallback(async () => { | ||||
|   const attachmentOnPress = () => { | ||||
|     if (composeState.poll.active) return | ||||
|  | ||||
|     if ( | ||||
|       composeState.attachments.uploads.length < | ||||
|       instanceConfigurationStatusMaxAttachments | ||||
|     ) { | ||||
|     if (composeState.attachments.uploads.length < instanceConfigurationStatusMaxAttachments) { | ||||
|       analytics('compose_actions_attachment_press', { | ||||
|         count: composeState.attachments.uploads.length | ||||
|       }) | ||||
|       return await chooseAndUploadAttachment({ | ||||
|       return chooseAndUploadAttachment({ | ||||
|         composeDispatch, | ||||
|         showActionSheetWithOptions | ||||
|       }) | ||||
|     } | ||||
|   }, [composeState.poll.active, composeState.attachments.uploads]) | ||||
|   } | ||||
|  | ||||
|   const pollColor = useMemo(() => { | ||||
|     if (composeState.attachments.uploads.length) return colors.disabled | ||||
| @@ -56,7 +54,7 @@ const ComposeActions: React.FC = () => { | ||||
|       return colors.secondary | ||||
|     } | ||||
|   }, [composeState.poll.active, composeState.attachments.uploads]) | ||||
|   const pollOnPress = useCallback(() => { | ||||
|   const pollOnPress = () => { | ||||
|     if (!composeState.attachments.uploads.length) { | ||||
|       analytics('compose_actions_poll_press', { | ||||
|         current: composeState.poll.active | ||||
| @@ -70,7 +68,7 @@ const ComposeActions: React.FC = () => { | ||||
|     if (composeState.poll.active) { | ||||
|       composeState.textInputFocus.refs.text.current?.focus() | ||||
|     } | ||||
|   }, [composeState.poll.active, composeState.attachments.uploads]) | ||||
|   } | ||||
|  | ||||
|   const visibilityIcon = useMemo(() => { | ||||
|     switch (composeState.visibility) { | ||||
| @@ -84,7 +82,7 @@ const ComposeActions: React.FC = () => { | ||||
|         return 'Mail' | ||||
|     } | ||||
|   }, [composeState.visibility]) | ||||
|   const visibilityOnPress = useCallback(() => { | ||||
|   const visibilityOnPress = () => { | ||||
|     if (!composeState.visibilityLock) { | ||||
|       showActionSheetWithOptions( | ||||
|         { | ||||
| @@ -133,9 +131,9 @@ const ComposeActions: React.FC = () => { | ||||
|         } | ||||
|       ) | ||||
|     } | ||||
|   }, [composeState.visibility]) | ||||
|   } | ||||
|  | ||||
|   const spoilerOnPress = useCallback(() => { | ||||
|   const spoilerOnPress = () => { | ||||
|     analytics('compose_actions_spoiler_press', { | ||||
|       current: composeState.spoiler.active | ||||
|     }) | ||||
| @@ -147,29 +145,45 @@ const ComposeActions: React.FC = () => { | ||||
|       type: 'spoiler', | ||||
|       payload: { active: !composeState.spoiler.active } | ||||
|     }) | ||||
|   }, [composeState.spoiler.active, composeState.textInputFocus]) | ||||
|   } | ||||
|  | ||||
|   const { emojisState, emojisDispatch } = useContext(EmojisContext) | ||||
|   const emojiColor = useMemo(() => { | ||||
|     if (!composeState.emoji.emojis) return colors.disabled | ||||
|     if (!emojisState.emojis.length) return colors.disabled | ||||
|  | ||||
|     if (composeState.emoji.active) { | ||||
|     if (emojisState.targetIndex !== -1) { | ||||
|       return colors.primaryDefault | ||||
|     } else { | ||||
|       return colors.secondary | ||||
|     } | ||||
|   }, [composeState.emoji.active, composeState.emoji.emojis]) | ||||
|   const emojiOnPress = useCallback(() => { | ||||
|     analytics('compose_actions_emojis_press', { | ||||
|       current: composeState.emoji.active | ||||
|     }) | ||||
|     if (composeState.emoji.emojis) { | ||||
|       layoutAnimation() | ||||
|       composeDispatch({ | ||||
|         type: 'emoji', | ||||
|         payload: { ...composeState.emoji, active: !composeState.emoji.active } | ||||
|       }) | ||||
|   }, [emojisState.emojis.length, emojisState.targetIndex]) | ||||
|   // useEffect(() => { | ||||
|   //   const showSubscription = Keyboard.addListener('keyboardWillShow', () => { | ||||
|   //     composeDispatch({ type: 'emoji/shown', payload: false }) | ||||
|   //   }) | ||||
|  | ||||
|   //   return () => { | ||||
|   //     showSubscription.remove() | ||||
|   //   } | ||||
|   // }, []) | ||||
|   const emojiOnPress = () => { | ||||
|     if (emojisState.targetIndex === -1) { | ||||
|       Keyboard.dismiss() | ||||
|     } | ||||
|   }, [composeState.emoji.active, composeState.emoji.emojis]) | ||||
|     const focusedPropsIndex = emojisState.inputProps?.findIndex(props => props.isFocused.current) | ||||
|     if (focusedPropsIndex === -1) return | ||||
|     emojisDispatch({ type: 'target', payload: focusedPropsIndex }) | ||||
|     // Keyboard.dismiss() | ||||
|     // analytics('compose_actions_emojis_press', { | ||||
|     //   current: composeState.emoji.active | ||||
|     // }) | ||||
|     // if (composeState.emoji.emojis) { | ||||
|     //   composeDispatch({ | ||||
|     //     type: 'emoji', | ||||
|     //     payload: { ...composeState.emoji, active: !composeState.emoji.active } | ||||
|     //   }) | ||||
|     // } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <View | ||||
| @@ -186,12 +200,8 @@ const ComposeActions: React.FC = () => { | ||||
|     > | ||||
|       <Pressable | ||||
|         accessibilityRole='button' | ||||
|         accessibilityLabel={t( | ||||
|           'content.root.actions.attachment.accessibilityLabel' | ||||
|         )} | ||||
|         accessibilityHint={t( | ||||
|           'content.root.actions.attachment.accessibilityHint' | ||||
|         )} | ||||
|         accessibilityLabel={t('content.root.actions.attachment.accessibilityLabel')} | ||||
|         accessibilityHint={t('content.root.actions.attachment.accessibilityHint')} | ||||
|         accessibilityState={{ | ||||
|           disabled: composeState.poll.active | ||||
|         }} | ||||
| @@ -213,10 +223,9 @@ const ComposeActions: React.FC = () => { | ||||
|       /> | ||||
|       <Pressable | ||||
|         accessibilityRole='button' | ||||
|         accessibilityLabel={t( | ||||
|           'content.root.actions.visibility.accessibilityLabel', | ||||
|           { visibility: composeState.visibility } | ||||
|         )} | ||||
|         accessibilityLabel={t('content.root.actions.visibility.accessibilityLabel', { | ||||
|           visibility: composeState.visibility | ||||
|         })} | ||||
|         accessibilityState={{ disabled: composeState.visibilityLock }} | ||||
|         style={styles.button} | ||||
|         onPress={visibilityOnPress} | ||||
| @@ -224,17 +233,13 @@ const ComposeActions: React.FC = () => { | ||||
|           <Icon | ||||
|             name={visibilityIcon} | ||||
|             size={24} | ||||
|             color={ | ||||
|               composeState.visibilityLock ? colors.disabled : colors.secondary | ||||
|             } | ||||
|             color={composeState.visibilityLock ? colors.disabled : colors.secondary} | ||||
|           /> | ||||
|         } | ||||
|       /> | ||||
|       <Pressable | ||||
|         accessibilityRole='button' | ||||
|         accessibilityLabel={t( | ||||
|           'content.root.actions.spoiler.accessibilityLabel' | ||||
|         )} | ||||
|         accessibilityLabel={t('content.root.actions.spoiler.accessibilityLabel')} | ||||
|         accessibilityState={{ expanded: composeState.spoiler.active }} | ||||
|         style={styles.button} | ||||
|         onPress={spoilerOnPress} | ||||
| @@ -242,11 +247,7 @@ const ComposeActions: React.FC = () => { | ||||
|           <Icon | ||||
|             name='AlertTriangle' | ||||
|             size={24} | ||||
|             color={ | ||||
|               composeState.spoiler.active | ||||
|                 ? colors.primaryDefault | ||||
|                 : colors.secondary | ||||
|             } | ||||
|             color={composeState.spoiler.active ? colors.primaryDefault : colors.secondary} | ||||
|           /> | ||||
|         } | ||||
|       /> | ||||
| @@ -255,8 +256,8 @@ const ComposeActions: React.FC = () => { | ||||
|         accessibilityLabel={t('content.root.actions.emoji.accessibilityLabel')} | ||||
|         accessibilityHint={t('content.root.actions.emoji.accessibilityHint')} | ||||
|         accessibilityState={{ | ||||
|           disabled: composeState.emoji.emojis ? false : true, | ||||
|           expanded: composeState.emoji.active | ||||
|           disabled: emojisState.emojis.length ? false : true, | ||||
|           expanded: emojisState.targetIndex !== -1 | ||||
|         }} | ||||
|         style={styles.button} | ||||
|         onPress={emojiOnPress} | ||||
|   | ||||
| @@ -1,31 +1,21 @@ | ||||
| import ComposeAttachments from '@screens/Compose/Root/Footer/Attachments' | ||||
| import ComposeEmojis from '@screens/Compose/Root/Footer/Emojis' | ||||
| import ComposePoll from '@screens/Compose/Root/Footer/Poll' | ||||
| import ComposeReply from '@screens/Compose/Root/Footer/Reply' | ||||
| import ComposeContext from '@screens/Compose/utils/createContext' | ||||
| import React, { RefObject, useContext } from 'react' | ||||
| import { SectionList, View } from 'react-native' | ||||
| import { View } from 'react-native' | ||||
|  | ||||
| export interface Props { | ||||
|   accessibleRefAttachments: RefObject<View> | ||||
|   accessibleRefEmojis: RefObject<SectionList> | ||||
| } | ||||
|  | ||||
| const ComposeRootFooter: React.FC<Props> = ({ | ||||
|   accessibleRefAttachments, | ||||
|   accessibleRefEmojis | ||||
| }) => { | ||||
| const ComposeRootFooter: React.FC<Props> = ({ accessibleRefAttachments }) => { | ||||
|   const { composeState } = useContext(ComposeContext) | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {composeState.emoji.active ? ( | ||||
|         <ComposeEmojis accessibleRefEmojis={accessibleRefEmojis} /> | ||||
|       ) : null} | ||||
|       {composeState.attachments.uploads.length ? ( | ||||
|         <ComposeAttachments | ||||
|           accessibleRefAttachments={accessibleRefAttachments} | ||||
|         /> | ||||
|         <ComposeAttachments accessibleRefAttachments={accessibleRefAttachments} /> | ||||
|       ) : null} | ||||
|       {composeState.poll.active ? <ComposePoll /> : null} | ||||
|       {composeState.replyToStatus ? <ComposeReply /> : null} | ||||
|   | ||||
| @@ -26,9 +26,8 @@ const updateText = ({ | ||||
|     const whiteSpaceFront = /\s/g.test(contentFront.slice(-1)) | ||||
|     const whiteSpaceRear = /\s/g.test(contentRear.slice(-1)) | ||||
|  | ||||
|     const newTextWithSpace = `${ | ||||
|       whiteSpaceFront || type === 'suggestion' ? '' : ' ' | ||||
|     }${newText}${whiteSpaceRear ? '' : ' '}` | ||||
|     const newTextWithSpace = `${whiteSpaceFront || type === 'suggestion' ? '' : ' ' | ||||
|       }${newText}${whiteSpaceRear ? '' : ' '}` | ||||
|  | ||||
|     formatText({ | ||||
|       textInput, | ||||
|   | ||||
| @@ -9,16 +9,15 @@ const composeInitialState: Omit<ComposeState, 'timestamp'> = { | ||||
|     count: 0, | ||||
|     raw: '', | ||||
|     formatted: undefined, | ||||
|     selection: { start: 0, end: 0 } | ||||
|     selection: { start: 0 } | ||||
|   }, | ||||
|   text: { | ||||
|     count: 0, | ||||
|     raw: '', | ||||
|     formatted: undefined, | ||||
|     selection: { start: 0, end: 0 } | ||||
|     selection: { start: 0 } | ||||
|   }, | ||||
|   tag: undefined, | ||||
|   emoji: { active: false, emojis: undefined }, | ||||
|   poll: { | ||||
|     active: false, | ||||
|     total: 2, | ||||
|   | ||||
| @@ -35,8 +35,6 @@ const composeReducer = ( | ||||
|       return { ...state, text: { ...state.text, ...action.payload } } | ||||
|     case 'tag': | ||||
|       return { ...state, tag: action.payload } | ||||
|     case 'emoji': | ||||
|       return { ...state, emoji: action.payload } | ||||
|     case 'poll': | ||||
|       return { ...state, poll: { ...state.poll, ...action.payload } } | ||||
|     case 'attachments/sensitive': | ||||
|   | ||||
							
								
								
									
										17
									
								
								src/screens/Compose/utils/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								src/screens/Compose/utils/types.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -26,13 +26,13 @@ export type ComposeState = { | ||||
|     count: number | ||||
|     raw: string | ||||
|     formatted: ReactNode | ||||
|     selection: { start: number; end: number } | ||||
|     selection: { start: number; end?: number } | ||||
|   } | ||||
|   text: { | ||||
|     count: number | ||||
|     raw: string | ||||
|     formatted: ReactNode | ||||
|     selection: { start: number; end: number } | ||||
|     selection: { start: number; end?: number } | ||||
|   } | ||||
|   tag?: { | ||||
|     type: 'url' | 'accounts' | 'hashtags' | ||||
| @@ -40,15 +40,6 @@ export type ComposeState = { | ||||
|     offset: number | ||||
|     length: number | ||||
|   } | ||||
|   emoji: { | ||||
|     active: boolean | ||||
|     emojis: | ||||
|     | { | ||||
|       title: string | ||||
|       data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][] | ||||
|     }[] | ||||
|     | undefined | ||||
|   } | ||||
|   poll: { | ||||
|     active: boolean | ||||
|     total: number | ||||
| @@ -96,10 +87,6 @@ export type ComposeAction = | ||||
|     type: 'tag' | ||||
|     payload: ComposeState['tag'] | ||||
|   } | ||||
|   | { | ||||
|     type: 'emoji' | ||||
|     payload: ComposeState['emoji'] | ||||
|   } | ||||
|   | { | ||||
|     type: 'poll' | ||||
|     payload: Partial<ComposeState['poll']> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user