tooot/src/screens/Shared/Compose.tsx

251 lines
7.1 KiB
TypeScript
Raw Normal View History

2020-12-29 16:19:04 +01:00
import { HeaderLeft, HeaderRight } from '@components/Header'
2020-12-30 00:56:25 +01:00
import { toast } from '@root/components/toast'
2020-12-29 16:19:04 +01:00
import { store } from '@root/store'
2020-12-30 00:56:25 +01:00
import layoutAnimation from '@root/utils/styles/layoutAnimation'
2020-12-29 16:19:04 +01:00
import formatText from '@screens/Shared/Compose/formatText'
import ComposeRoot from '@screens/Shared/Compose/Root'
import { getLocalAccountPreferences } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, {
createContext,
Dispatch,
2020-12-26 00:53:49 +01:00
useCallback,
useEffect,
useReducer,
useState
} from 'react'
2020-12-06 23:51:13 +01:00
import {
Alert,
Keyboard,
KeyboardAvoidingView,
StyleSheet,
2020-12-30 00:56:25 +01:00
Text
2020-12-06 23:51:13 +01:00
} from 'react-native'
2020-11-15 23:33:01 +01:00
import { SafeAreaView } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
2020-12-20 17:53:24 +01:00
import { useQueryClient } from 'react-query'
2020-12-30 00:56:25 +01:00
import ComposeEditAttachment from './Compose/EditAttachment'
import ComposeEditAttachmentRoot from './Compose/EditAttachment/Root'
import composeInitialState from './Compose/utils/initialState'
import composeParseState from './Compose/utils/parseState'
import composeSend from './Compose/utils/post'
import composeReducer from './Compose/utils/reducer'
import { ComposeAction, ComposeState } from './Compose/utils/types'
2020-11-15 20:29:43 +01:00
const Stack = createNativeStackNavigator()
type ContextType = {
composeState: ComposeState
composeDispatch: Dispatch<ComposeAction>
}
export const ComposeContext = createContext<ContextType>({} as ContextType)
2020-12-07 12:31:40 +01:00
export interface Props {
route: {
params:
| {
2020-12-13 01:24:25 +01:00
type?: 'reply' | 'conversation' | 'edit'
2020-12-07 12:31:40 +01:00
incomingStatus: Mastodon.Status
2020-12-21 21:47:15 +01:00
visibilityLock?: boolean
2020-12-07 12:31:40 +01:00
}
| undefined
}
navigation: any
2020-12-07 12:31:40 +01:00
}
const Compose: React.FC<Props> = ({ route: { params }, navigation }) => {
2020-12-07 12:31:40 +01:00
const { theme } = useTheme()
2020-12-20 17:53:24 +01:00
const queryClient = useQueryClient()
2020-12-06 23:51:13 +01:00
2020-11-15 23:33:01 +01:00
const [hasKeyboard, setHasKeyboard] = useState(false)
useEffect(() => {
Keyboard.addListener('keyboardWillShow', _keyboardDidShow)
Keyboard.addListener('keyboardWillHide', _keyboardDidHide)
// cleanup function
return () => {
Keyboard.removeListener('keyboardWillShow', _keyboardDidShow)
Keyboard.removeListener('keyboardWillHide', _keyboardDidHide)
}
}, [])
const _keyboardDidShow = () => {
setHasKeyboard(true)
}
const _keyboardDidHide = () => {
setHasKeyboard(false)
}
2020-12-07 12:31:40 +01:00
const [composeState, composeDispatch] = useReducer(
composeReducer,
2020-12-07 12:31:40 +01:00
params?.type && params?.incomingStatus
2020-12-30 00:56:25 +01:00
? composeParseState({
2020-12-07 12:31:40 +01:00
type: params.type,
2020-12-21 21:47:15 +01:00
incomingStatus: params.incomingStatus,
visibilityLock: params.visibilityLock
2020-12-07 12:31:40 +01:00
})
: {
...composeInitialState,
visibility: getLocalAccountPreferences(store.getState())[
'posting:default:visibility'
] as ComposeState['visibility']
}
2020-12-07 12:31:40 +01:00
)
const [isSubmitting, setIsSubmitting] = useState(false)
useEffect(() => {
switch (params?.type) {
case 'edit':
if (params.incomingStatus.spoiler_text) {
formatText({
textInput: 'spoiler',
2020-12-07 12:31:40 +01:00
composeDispatch,
content: params.incomingStatus.spoiler_text,
disableDebounce: true
})
}
formatText({
textInput: 'text',
2020-12-07 12:31:40 +01:00
composeDispatch,
content: params.incomingStatus.text!,
disableDebounce: true
})
break
case 'reply':
2020-12-13 01:24:25 +01:00
const actualStatus =
params.incomingStatus.reblog || params.incomingStatus
const allMentions = actualStatus.mentions.map(
mention => `@${mention.acct}`
)
let replyPlaceholder = allMentions.join(' ')
if (replyPlaceholder.length === 0) {
replyPlaceholder = `@${actualStatus.account.acct} `
} else {
replyPlaceholder = replyPlaceholder + ' '
}
2020-12-07 12:31:40 +01:00
formatText({
textInput: 'text',
2020-12-07 12:31:40 +01:00
composeDispatch,
2020-12-13 01:24:25 +01:00
content: replyPlaceholder,
2020-12-07 12:31:40 +01:00
disableDebounce: true
})
break
2020-12-21 21:47:15 +01:00
case 'conversation':
formatText({
textInput: 'text',
composeDispatch,
content: `@${params.incomingStatus.account.acct} `,
disableDebounce: true
})
break
2020-12-07 12:31:40 +01:00
}
}, [params?.type])
2020-11-15 22:33:09 +01:00
2020-12-06 23:51:13 +01:00
const totalTextCount =
2020-12-07 12:31:40 +01:00
(composeState.spoiler.active ? composeState.spoiler.count : 0) +
composeState.text.count
2020-12-06 23:51:13 +01:00
2020-12-13 01:24:25 +01:00
const postButtonText = {
conversation: '回复私信',
reply: '发布回复',
edit: '发嘟嘟'
}
2020-12-26 00:53:49 +01:00
const headerLeft = useCallback(
() => (
<HeaderLeft
2020-12-26 23:27:53 +01:00
type='text'
content='退出编辑'
2020-12-26 00:53:49 +01:00
onPress={() =>
Alert.alert('确认取消编辑?', '', [
{
text: '退出编辑',
style: 'destructive',
onPress: () => navigation.goBack()
2020-12-30 00:56:25 +01:00
},
{ text: '继续编辑', style: 'cancel' }
2020-12-26 00:53:49 +01:00
])
}
/>
),
[]
)
const headerCenter = useCallback(
() => (
<Text
style={[
styles.count,
{
color: totalTextCount > 500 ? theme.red : theme.secondary
}
]}
>
{totalTextCount} / 500
</Text>
),
[totalTextCount]
)
const headerRight = useCallback(
2020-12-26 23:27:53 +01:00
() => (
<HeaderRight
type='text'
content={params?.type ? postButtonText[params.type] : '发嘟嘟'}
2020-12-30 00:56:25 +01:00
onPress={() => {
layoutAnimation()
setIsSubmitting(true)
composeSend(params, composeState)
.then(() => {
queryClient.invalidateQueries(['Following'])
navigation.goBack()
toast({ type: 'success', content: '发布成功' })
})
.catch(() => {
setIsSubmitting(false)
Alert.alert('发布失败', '', [
{
text: '返回重试'
}
])
})
}}
2020-12-26 23:27:53 +01:00
loading={isSubmitting}
2020-12-30 00:56:25 +01:00
disabled={composeState.text.raw.length < 1 || totalTextCount > 500}
2020-12-26 23:27:53 +01:00
/>
),
2020-12-30 00:56:25 +01:00
[isSubmitting, composeState.text.raw, totalTextCount]
2020-12-29 16:19:04 +01:00
)
return (
2020-11-15 23:33:01 +01:00
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
<SafeAreaView
style={{ flex: 1 }}
edges={hasKeyboard ? ['left', 'right'] : ['left', 'right', 'bottom']}
>
2020-12-30 00:56:25 +01:00
<ComposeContext.Provider value={{ composeState, composeDispatch }}>
<Stack.Navigator>
<Stack.Screen
name='Screen-Shared-Compose-Root'
component={ComposeRoot}
options={{ headerLeft, headerCenter, headerRight }}
/>
<Stack.Screen
name='Screen-Shared-Compose-EditAttachment'
component={ComposeEditAttachment}
options={{ stackPresentation: 'modal' }}
/>
</Stack.Navigator>
</ComposeContext.Provider>
2020-11-15 23:33:01 +01:00
</SafeAreaView>
</KeyboardAvoidingView>
)
}
2020-12-06 23:51:13 +01:00
const styles = StyleSheet.create({
count: {
textAlign: 'center',
...StyleConstants.FontStyle.M
2020-12-06 23:51:13 +01:00
}
})
export default Compose