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