Use context to provide compose state and dispatch

This commit is contained in:
Zhiyuan Zheng 2020-12-11 00:29:22 +01:00
parent c114176ee4
commit 0fa9f87f66
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
15 changed files with 244 additions and 307 deletions

16
App.tsx
View File

@ -16,14 +16,14 @@ setConsole({
error: console.warn
})
if (__DEV__) {
const whyDidYouRender = require('@welldone-software/why-did-you-render')
whyDidYouRender(React, {
trackAllPureComponents: true,
trackHooks: true,
hotReloadBufferMs: 1000
})
}
// if (__DEV__) {
// const whyDidYouRender = require('@welldone-software/why-did-you-render')
// whyDidYouRender(React, {
// trackAllPureComponents: true,
// trackHooks: true,
// hotReloadBufferMs: 1000
// })
// }
const App: React.FC = () => {
return (

View File

@ -1,11 +1,22 @@
import React, { ReactNode, useEffect, useReducer, useState } from 'react'
import React, {
createContext,
createRef,
Dispatch,
ReactNode,
RefObject,
useEffect,
useReducer,
useRef,
useState
} from 'react'
import {
ActivityIndicator,
Alert,
Keyboard,
KeyboardAvoidingView,
StyleSheet,
Text
Text,
TextInput
} from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
@ -67,10 +78,17 @@ export type ComposeState = {
| '604800'
| string
}
attachments: { sensitive: boolean; uploads: Mastodon.Attachment[] }
attachments: {
sensitive: boolean
uploads: (Mastodon.Attachment & { local_url?: string })[]
}
attachmentUploadProgress?: { progress: number; aspect?: number }
visibility: 'public' | 'unlisted' | 'private' | 'direct'
replyToStatus?: Mastodon.Status
textInputFocus: {
current: 'text' | 'spoiler'
refs: { text: RefObject<TextInput>; spoiler: RefObject<TextInput> }
}
}
export type PostAction =
@ -110,6 +128,10 @@ export type PostAction =
type: 'visibility'
payload: ComposeState['visibility']
}
| {
type: 'textInputFocus'
payload: Partial<ComposeState['textInputFocus']>
}
const composeInitialState: ComposeState = {
spoiler: {
@ -145,7 +167,11 @@ const composeInitialState: ComposeState = {
getLocalAccountPreferences(store.getState())[
'posting:default:visibility'
] || 'public',
replyToStatus: undefined
replyToStatus: undefined,
textInputFocus: {
current: 'text',
refs: { text: createRef(), spoiler: createRef() }
}
}
const composeExistingState = ({
type,
@ -244,11 +270,22 @@ const postReducer = (state: ComposeState, action: PostAction): ComposeState => {
}
case 'visibility':
return { ...state, visibility: action.payload }
case 'textInputFocus':
return {
...state,
textInputFocus: { ...state.textInputFocus, ...action.payload }
}
default:
throw new Error('Unexpected action')
}
}
type ContextType = {
composeState: ComposeState
composeDispatch: Dispatch<PostAction>
}
export const ComposeContext = createContext<ContextType>({} as ContextType)
export interface Props {
route: {
params:
@ -298,14 +335,14 @@ const Compose: React.FC<Props> = ({ route: { params } }) => {
case 'edit':
if (params.incomingStatus.spoiler_text) {
formatText({
origin: 'spoiler',
textInput: 'spoiler',
composeDispatch,
content: params.incomingStatus.spoiler_text,
disableDebounce: true
})
}
formatText({
origin: 'text',
textInput: 'text',
composeDispatch,
content: params.incomingStatus.text!,
disableDebounce: true
@ -313,7 +350,7 @@ const Compose: React.FC<Props> = ({ route: { params } }) => {
break
case 'reply':
formatText({
origin: 'text',
textInput: 'text',
composeDispatch,
content: `@${
params.incomingStatus.reblog
@ -429,6 +466,8 @@ const Compose: React.FC<Props> = ({ route: { params } }) => {
const totalTextCount =
(composeState.spoiler.active ? composeState.spoiler.count : 0) +
composeState.text.count
// doesn't work
const rawCount = composeState.text.raw.length
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
@ -475,18 +514,17 @@ const Compose: React.FC<Props> = ({ route: { params } }) => {
<HeaderRight
onPress={async () => tootPost()}
text='发嘟嘟'
disabled={
composeState.text.raw.length < 1 || totalTextCount > 500
}
disabled={rawCount < 1 || totalTextCount > 500}
/>
)
}}
>
{() => (
<ComposeRoot
composeState={composeState}
composeDispatch={composeDispatch}
/>
<ComposeContext.Provider
value={{ composeState, composeDispatch }}
>
<ComposeRoot />
</ComposeContext.Provider>
)}
</Stack.Screen>
</Stack.Navigator>
@ -502,4 +540,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(Compose, () => true)
export default Compose

View File

@ -1,22 +1,13 @@
import { Feather } from '@expo/vector-icons'
import React, { Dispatch, useCallback, useMemo } from 'react'
import { ActionSheetIOS, StyleSheet, TextInput, View } from 'react-native'
import React, { useCallback, useContext, useMemo } from 'react'
import { ActionSheetIOS, StyleSheet, View } from 'react-native'
import { StyleConstants } from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { PostAction, ComposeState } from '../Compose'
import { ComposeContext } from '../Compose'
import addAttachments from './addAttachments'
export interface Props {
textInputRef: React.RefObject<TextInput>
composeState: ComposeState
composeDispatch: Dispatch<PostAction>
}
const ComposeActions: React.FC<Props> = ({
textInputRef,
composeState,
composeDispatch
}) => {
const ComposeActions: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const { theme } = useTheme()
const attachmentColor = useMemo(() => {
@ -71,7 +62,7 @@ const ComposeActions: React.FC<Props> = ({
})
}
if (composeState.poll.active) {
textInputRef.current?.focus()
composeState.textInputFocus.refs.text.current?.focus()
}
}, [
composeState.poll.active,

View File

@ -1,4 +1,4 @@
import React, { Dispatch, useCallback } from 'react'
import React, { useCallback, useContext } from 'react'
import {
FlatList,
Image,
@ -8,7 +8,7 @@ import {
View
} from 'react-native'
import { PostAction, ComposeState } from '../Compose'
import { ComposeContext } from '../Compose'
import { StyleConstants } from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { useNavigation } from '@react-navigation/native'
@ -19,12 +19,8 @@ import { Feather } from '@expo/vector-icons'
const DEFAULT_HEIGHT = 200
export interface Props {
composeState: ComposeState
composeDispatch: Dispatch<PostAction>
}
const ComposeAttachments: React.FC<Props> = ({ composeState, composeDispatch }) => {
const ComposeAttachments: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const { theme } = useTheme()
const navigation = useNavigation()
@ -106,7 +102,8 @@ const ComposeAttachments: React.FC<Props> = ({ composeState, composeDispatch })
style={styles.progressContainer}
visible={composeState.attachmentUploadProgress === undefined}
width={
(composeState.attachmentUploadProgress?.aspect || 3 / 2) * DEFAULT_HEIGHT
(composeState.attachmentUploadProgress?.aspect || 3 / 2) *
DEFAULT_HEIGHT
}
height={200}
>

View File

@ -349,4 +349,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(ComposeEditAttachment, () => true)
export default ComposeEditAttachment

View File

@ -1,34 +1,22 @@
import React, { Dispatch, useCallback, useMemo } from 'react'
import React, { useCallback, useContext, useMemo } from 'react'
import {
Image,
Pressable,
SectionList,
StyleSheet,
Text,
TextInput,
View
} from 'react-native'
import { StyleConstants } from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { PostAction, ComposeState } from '../Compose'
import { ComposeContext } from '../Compose'
import updateText from './updateText'
export interface Props {
textInputRef: React.RefObject<TextInput>
composeState: ComposeState
composeDispatch: Dispatch<PostAction>
}
const SingleEmoji = ({
emoji,
textInputRef,
composeState,
composeDispatch
}: { emoji: Mastodon.Emoji } & Props) => {
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const onPress = useCallback(() => {
updateText({
origin: textInputRef.current?.isFocused() ? 'text' : 'spoiler',
composeState,
composeDispatch,
newText: `:${emoji.shortcode}:`,
@ -48,7 +36,8 @@ const SingleEmoji = ({
)
}
const ComposeEmojis: React.FC<Props> = ({ ...props }) => {
const ComposeEmojis: React.FC = () => {
const { composeState } = useContext(ComposeContext)
const { theme } = useTheme()
const listHeader = useCallback(
@ -61,7 +50,7 @@ const ComposeEmojis: React.FC<Props> = ({ ...props }) => {
const emojiList = useCallback(
section =>
section.data.map((emoji: Mastodon.Emoji) => (
<SingleEmoji key={emoji.shortcode} emoji={emoji} {...props} />
<SingleEmoji key={emoji.shortcode} emoji={emoji} />
)),
[]
)
@ -80,7 +69,7 @@ const ComposeEmojis: React.FC<Props> = ({ ...props }) => {
<SectionList
horizontal
keyboardShouldPersistTaps='handled'
sections={props.composeState.emoji.emojis!}
sections={composeState.emoji.emojis!}
keyExtractor={item => item.shortcode}
renderSectionHeader={listHeader}
renderItem={listItem}
@ -116,4 +105,4 @@ const styles = StyleSheet.create({
}
})
export default ComposeEmojis
export default React.memo(ComposeEmojis, () => true)

View File

@ -1,22 +1,20 @@
import React, { Dispatch, useCallback, useEffect, useState } from 'react'
import React, { useContext, useEffect, useState } from 'react'
import { ActionSheetIOS, StyleSheet, TextInput, View } from 'react-native'
import { Feather } from '@expo/vector-icons'
import { PostAction, ComposeState } from '../Compose'
import { ComposeContext } from '../Compose'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { StyleConstants } from 'src/utils/styles/constants'
import { ButtonRow } from 'src/components/Button'
import { MenuContainer, MenuRow } from 'src/components/Menu'
export interface Props {
poll: ComposeState['poll']
composeDispatch: Dispatch<PostAction>
}
const ComposePoll: React.FC<Props> = ({
poll: { total, options, multiple, expire },
composeDispatch
}) => {
const ComposePoll: React.FC = () => {
const {
composeState: {
poll: { total, options, multiple, expire }
},
composeDispatch
} = useContext(ComposeContext)
const { theme } = useTheme()
const expireMapping: { [key: string]: string } = {
@ -34,24 +32,6 @@ const ComposePoll: React.FC<Props> = ({
setFirstRender(false)
}, [])
const minusOnPress = useCallback(
() =>
total > 2 &&
composeDispatch({
type: 'poll',
payload: { total: total - 1 }
}),
[total]
)
console.log('total: ', total)
const plusOnPress = useCallback(() => {
total < 4 &&
composeDispatch({
type: 'poll',
payload: { total: total + 1 }
})
}, [total])
return (
<View style={[styles.base, { borderColor: theme.border }]}>
<View style={styles.options}>
@ -101,14 +81,28 @@ const ComposePoll: React.FC<Props> = ({
<View style={styles.controlAmount}>
<View style={styles.firstButton}>
<ButtonRow
onPress={minusOnPress}
key={total + 'minus'}
onPress={() => {
total > 2 &&
composeDispatch({
type: 'poll',
payload: { total: total - 1 }
})
}}
icon='minus'
disabled={!(total > 2)}
buttonSize='S'
/>
</View>
<ButtonRow
onPress={plusOnPress}
key={total + 'plus'}
onPress={() => {
total < 4 &&
composeDispatch({
type: 'poll',
payload: { total: total + 1 }
})
}}
icon='plus'
disabled={!(total < 4)}
buttonSize='S'

View File

@ -1,6 +1,5 @@
import React, { Dispatch, RefObject } from 'react'
import { StyleSheet, Text, TextInput, View } from 'react-native'
import { StyleConstants } from 'src/utils/styles/constants'
import React, { useContext } from 'react'
import { StyleSheet, View } from 'react-native'
import { useTheme } from 'src/utils/styles/ThemeManager'
import TimelineAttachment from 'src/components/Timelines/Timeline/Shared/Attachment'
@ -8,26 +7,26 @@ import TimelineAvatar from 'src/components/Timelines/Timeline/Shared/Avatar'
import TimelineCard from 'src/components/Timelines/Timeline/Shared/Card'
import TimelineContent from 'src/components/Timelines/Timeline/Shared/Content'
import TimelineHeaderDefault from 'src/components/Timelines/Timeline/Shared/HeaderDefault'
import { ComposeContext } from '../Compose'
export interface Props {
replyToStatus: Mastodon.Status
}
const ComposeReply: React.FC<Props> = ({ replyToStatus }) => {
const ComposeReply: React.FC = () => {
const {
composeState: { replyToStatus }
} = useContext(ComposeContext)
const { theme } = useTheme()
return (
<View style={styles.status}>
<TimelineAvatar account={replyToStatus.account} />
<TimelineAvatar account={replyToStatus!.account} />
<View style={styles.details}>
<TimelineHeaderDefault status={replyToStatus} />
{replyToStatus.content.length > 0 && (
<TimelineContent status={replyToStatus} />
<TimelineHeaderDefault status={replyToStatus!} />
{replyToStatus!.content.length > 0 && (
<TimelineContent status={replyToStatus!} />
)}
{replyToStatus.media_attachments.length > 0 && (
<TimelineAttachment status={replyToStatus} width={200} />
{replyToStatus!.media_attachments.length > 0 && (
<TimelineAttachment status={replyToStatus!} width={200} />
)}
{replyToStatus.card && <TimelineCard card={replyToStatus.card} />}
{replyToStatus!.card && <TimelineCard card={replyToStatus!.card} />}
</View>
</View>
)

View File

@ -3,6 +3,7 @@ import React, {
Dispatch,
RefObject,
useCallback,
useContext,
useEffect,
useMemo,
useRef
@ -24,21 +25,12 @@ import { emojisFetch } from 'src/utils/fetches/emojisFetch'
import { searchFetch } from 'src/utils/fetches/searchFetch'
import { StyleConstants } from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { PostAction, ComposeState } from '../Compose'
import { PostAction, ComposeState, ComposeContext } from '../Compose'
import ComposeActions from './Actions'
import ComposeAttachments from './Attachments'
import ComposeEmojis from './Emojis'
import ComposePoll from './Poll'
import ComposeReply from './Reply'
import ComposeSpoilerInput from './SpoilerInput'
import ComposeTextInput from './TextInput'
import updateText from './updateText'
import * as Permissions from 'expo-permissions'
export interface Props {
composeState: ComposeState
composeDispatch: Dispatch<PostAction>
}
import ComposeRootFooter from './Root/Footer'
import ComposeRootHeader from './Root/Header'
const ListItem = React.memo(
({
@ -58,7 +50,6 @@ const ListItem = React.memo(
? 'text'
: 'spoiler'
updateText({
origin: focusedInput,
composeState: {
...composeState,
[focusedInput]: {
@ -117,7 +108,9 @@ const ListItem = React.memo(
() => true
)
const ComposeRoot: React.FC<Props> = ({ composeState, composeDispatch }) => {
const ComposeRoot: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const { isFetching, isSuccess, data, refetch } = useQuery(
[
'Search',
@ -172,112 +165,6 @@ const ComposeRoot: React.FC<Props> = ({ composeState, composeDispatch }) => {
}
}, [isFetching])
const listHeader = useMemo(() => {
return (
<>
{composeState.spoiler.active ? (
<ComposeSpoilerInput
composeState={composeState}
composeDispatch={composeDispatch}
/>
) : null}
<ComposeTextInput
composeState={composeState}
composeDispatch={composeDispatch}
textInputRef={textInputRef}
/>
</>
)
}, [composeState.spoiler.active, composeState.text.formatted])
const listFooterEmojis = useMemo(
() =>
composeState.emoji.active && (
<View style={styles.emojis}>
<ComposeEmojis
textInputRef={textInputRef}
composeState={composeState}
composeDispatch={composeDispatch}
/>
</View>
),
[composeState.emoji.active]
)
const listFooterAttachments = useMemo(
() =>
(composeState.attachments.uploads.length > 0 ||
composeState.attachmentUploadProgress) && (
<View style={styles.attachments}>
<ComposeAttachments
composeState={composeState}
composeDispatch={composeDispatch}
/>
</View>
),
[composeState.attachments.uploads, composeState.attachmentUploadProgress]
)
// const listFooterPoll = useMemo(
// () =>
// composeState.poll.active && (
// <View style={styles.poll}>
// <ComposePoll
// poll={composeState.poll}
// composeDispatch={composeDispatch}
// />
// </View>
// ),
// [
// composeState.poll.active,
// composeState.poll.total,
// composeState.poll.options['0'],
// composeState.poll.options['1'],
// composeState.poll.options['2'],
// composeState.poll.options['3'],
// composeState.poll.multiple,
// composeState.poll.expire
// ]
// )
const listFooterPoll = () =>
composeState.poll.active && (
<View style={styles.poll}>
<ComposePoll
poll={composeState.poll}
composeDispatch={composeDispatch}
/>
</View>
)
const listFooterReply = useMemo(
() =>
composeState.replyToStatus && (
<View style={styles.replyTo}>
<ComposeReply replyToStatus={composeState.replyToStatus} />
</View>
),
[]
)
const listFooter = useMemo(() => {
return (
<>
{listFooterEmojis}
{listFooterAttachments}
{listFooterPoll()}
{listFooterReply}
</>
)
}, [
composeState.emoji.active,
composeState.attachments.uploads,
composeState.attachmentUploadProgress,
composeState.poll.active,
composeState.poll.total,
composeState.poll.options['0'],
composeState.poll.options['1'],
composeState.poll.options['2'],
composeState.poll.options['3'],
composeState.poll.multiple,
composeState.poll.expire
])
const listKey = useCallback(
(item: Mastodon.Account | Mastodon.Tag) => item.url,
[isSuccess]
@ -302,18 +189,14 @@ const ComposeRoot: React.FC<Props> = ({ composeState, composeDispatch }) => {
/>
<FlatList
keyboardShouldPersistTaps='handled'
ListHeaderComponent={listHeader}
ListFooterComponent={listFooter}
ListHeaderComponent={<ComposeRootHeader textInputRef={textInputRef} />}
ListFooterComponent={<ComposeRootFooter textInputRef={textInputRef} />}
ListEmptyComponent={listEmpty}
data={data}
keyExtractor={listKey}
renderItem={listItem}
/>
<ComposeActions
textInputRef={textInputRef}
composeState={composeState}
composeDispatch={composeDispatch}
/>
<ComposeActions />
</View>
)
}
@ -323,18 +206,6 @@ const styles = StyleSheet.create({
flex: 1
},
contentView: { flex: 1 },
attachments: {
flex: 1
},
poll: {
flex: 1,
padding: StyleConstants.Spacing.Global.PagePadding
},
replyTo: {
flex: 1,
padding: StyleConstants.Spacing.Global.PagePadding
},
suggestion: {
flex: 1
},
@ -374,9 +245,6 @@ const styles = StyleSheet.create({
fontSize: StyleConstants.Font.Size.S,
fontWeight: StyleConstants.Font.Weight.Bold,
marginBottom: StyleConstants.Spacing.XS
},
emojis: {
flex: 1
}
})

View File

@ -0,0 +1,64 @@
import React, { useContext } from 'react'
import { StyleSheet, TextInput, View } from 'react-native'
import { StyleConstants } from 'src/utils/styles/constants'
import { ComposeContext } from '../../Compose'
import ComposeAttachments from '../Attachments'
import ComposeEmojis from '../Emojis'
import ComposePoll from '../Poll'
import ComposeReply from '../Reply'
export interface Props {
textInputRef: React.RefObject<TextInput>
}
const ComposeRootFooter: React.FC<Props> = ({ textInputRef }) => {
const { composeState, composeDispatch } = useContext(ComposeContext)
return (
<>
{composeState.emoji.active && (
<View style={styles.emojis}>
<ComposeEmojis textInputRef={textInputRef} />
</View>
)}
{(composeState.attachments.uploads.length > 0 ||
composeState.attachmentUploadProgress) && (
<View style={styles.attachments}>
<ComposeAttachments />
</View>
)}
{composeState.poll.active && (
<View style={styles.poll} key='poll'>
<ComposePoll />
</View>
)}
{composeState.replyToStatus && (
<View style={styles.reply}>
<ComposeReply />
</View>
)}
</>
)
}
const styles = StyleSheet.create({
emojis: {
flex: 1
},
attachments: {
flex: 1
},
poll: {
flex: 1,
padding: StyleConstants.Spacing.Global.PagePadding
},
reply: {
flex: 1,
padding: StyleConstants.Spacing.Global.PagePadding
}
})
export default ComposeRootFooter

View File

@ -0,0 +1,17 @@
import React, { useContext } from 'react'
import { ComposeContext } from '../../Compose'
import ComposeSpoilerInput from '../SpoilerInput'
import ComposeTextInput from '../TextInput'
const ComposeRootHeader: React.FC = () => {
const { composeState } = useContext(ComposeContext)
return (
<>
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
<ComposeTextInput />
</>
)
}
export default ComposeRootHeader

View File

@ -1,19 +1,12 @@
import React, { Dispatch } from 'react'
import React, { useContext } from 'react'
import { StyleSheet, Text, TextInput } from 'react-native'
import { StyleConstants } from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { PostAction, ComposeState } from '../Compose'
import { ComposeContext } from '../Compose'
import formatText from './formatText'
export interface Props {
composeState: ComposeState
composeDispatch: Dispatch<PostAction>
}
const ComposeSpoilerInput: React.FC<Props> = ({
composeState,
composeDispatch,
}) => {
const ComposeSpoilerInput: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const { theme } = useTheme()
return (
@ -34,7 +27,7 @@ const ComposeSpoilerInput: React.FC<Props> = ({
placeholderTextColor={theme.secondary}
onChangeText={content =>
formatText({
origin: 'spoiler',
textInput: 'spoiler',
composeDispatch,
content
})
@ -49,7 +42,7 @@ const ComposeSpoilerInput: React.FC<Props> = ({
payload: { selection: { start, end } }
})
}}
// ref={textInputRef}
ref={composeState.textInputFocus.refs.spoiler}
scrollEnabled
>
<Text>{composeState.spoiler.formatted}</Text>
@ -68,8 +61,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(
ComposeSpoilerInput,
(prev, next) =>
prev.composeState.spoiler.formatted === next.composeState.spoiler.formatted
)
export default ComposeSpoilerInput

View File

@ -1,21 +1,12 @@
import React, { Dispatch, RefObject } from 'react'
import React, { useContext } from 'react'
import { StyleSheet, Text, TextInput } from 'react-native'
import { StyleConstants } from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { PostAction, ComposeState } from '../Compose'
import { ComposeContext } from '../Compose'
import formatText from './formatText'
export interface Props {
composeState: ComposeState
composeDispatch: Dispatch<PostAction>
textInputRef: RefObject<TextInput>
}
const ComposeTextInput: React.FC<Props> = ({
composeState,
composeDispatch,
textInputRef
}) => {
const ComposeTextInput: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const { theme } = useTheme()
return (
@ -36,11 +27,17 @@ const ComposeTextInput: React.FC<Props> = ({
placeholderTextColor={theme.secondary}
onChangeText={content =>
formatText({
origin: 'text',
textInput: 'text',
composeDispatch,
content
})
}
onFocus={() =>
composeDispatch({
type: 'textInputFocus',
payload: { current: 'text' }
})
}
onSelectionChange={({
nativeEvent: {
selection: { start, end }
@ -51,7 +48,7 @@ const ComposeTextInput: React.FC<Props> = ({
payload: { selection: { start, end } }
})
}}
ref={textInputRef}
ref={composeState.textInputFocus.refs.text}
scrollEnabled
>
<Text>{composeState.text.formatted}</Text>
@ -70,9 +67,4 @@ const styles = StyleSheet.create({
}
})
export default React.memo(
ComposeTextInput,
(prev, next) =>
prev.composeState.text.raw === next.composeState.text.raw &&
prev.composeState.text.formatted === next.composeState.text.formatted
)
export default ComposeTextInput

View File

@ -7,7 +7,7 @@ import { useTheme } from 'src/utils/styles/ThemeManager'
import { PostAction, ComposeState } from '../Compose'
export interface Params {
origin: 'text' | 'spoiler'
textInput: ComposeState['textInputFocus']['current']
composeDispatch: Dispatch<PostAction>
content: string
refetch?: (options?: RefetchOptions | undefined) => Promise<any>
@ -33,7 +33,7 @@ const debouncedSuggestions = debounce(
let prevTags: ComposeState['tag'][] = []
const formatText = ({
origin,
textInput,
composeDispatch,
content,
disableDebounce = false
@ -108,7 +108,7 @@ const formatText = ({
contentLength = contentLength + _content.length
composeDispatch({
type: origin,
type: textInput,
payload: {
count: contentLength,
raw: content,

View File

@ -3,25 +3,24 @@ import { PostAction, ComposeState } from '../Compose'
import formatText from './formatText'
const updateText = ({
origin,
composeState,
composeDispatch,
newText,
type
}: {
origin: 'text' | 'spoiler'
composeState: ComposeState
composeDispatch: Dispatch<PostAction>
newText: string
type: 'emoji' | 'suggestion'
}) => {
if (composeState[origin].raw.length) {
const contentFront = composeState[origin].raw.slice(
const textInput = composeState.textInputFocus.current
if (composeState[textInput].raw.length) {
const contentFront = composeState[textInput].raw.slice(
0,
composeState[origin].selection.start
composeState[textInput].selection.start
)
const contentRear = composeState[origin].raw.slice(
composeState[origin].selection.end
const contentRear = composeState[textInput].raw.slice(
composeState[textInput].selection.end
)
const whiteSpaceFront = /\s/g.test(contentFront.slice(-1))
@ -32,14 +31,14 @@ const updateText = ({
}${newText}${whiteSpaceRear ? '' : ' '}`
formatText({
origin,
textInput,
composeDispatch,
content: [contentFront, newTextWithSpace, contentRear].join(''),
disableDebounce: true
})
} else {
formatText({
origin,
textInput,
composeDispatch,
content: `${newText} `,
disableDebounce: true