2020-11-15 23:33:01 +01:00
|
|
|
import React, { ReactNode, useEffect, useReducer, useState } from 'react'
|
|
|
|
import {
|
|
|
|
Alert,
|
|
|
|
Keyboard,
|
|
|
|
KeyboardAvoidingView,
|
|
|
|
Pressable,
|
|
|
|
Text
|
|
|
|
} from 'react-native'
|
|
|
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
2020-11-08 01:10:38 +01:00
|
|
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
2020-11-08 19:13:46 +01:00
|
|
|
import { useNavigation } from '@react-navigation/native'
|
2020-11-08 01:10:38 +01:00
|
|
|
|
2020-11-21 13:19:05 +01:00
|
|
|
import store from 'src/store'
|
|
|
|
import PostMain from './Compose/PostMain'
|
2020-11-15 22:33:09 +01:00
|
|
|
import client from 'src/api/client'
|
2020-11-21 13:19:05 +01:00
|
|
|
import { getLocalAccountPreferences } from 'src/utils/slices/instancesSlice'
|
2020-11-08 01:10:38 +01:00
|
|
|
|
2020-11-15 20:29:43 +01:00
|
|
|
const Stack = createNativeStackNavigator()
|
2020-11-08 01:10:38 +01:00
|
|
|
|
2020-11-15 22:33:09 +01:00
|
|
|
export type PostState = {
|
|
|
|
text: {
|
|
|
|
count: number
|
|
|
|
raw: string
|
|
|
|
formatted: ReactNode
|
|
|
|
}
|
|
|
|
selection: { start: number; end: number }
|
|
|
|
overlay: null | 'suggestions' | 'emojis'
|
|
|
|
tag:
|
|
|
|
| {
|
|
|
|
type: 'url' | 'accounts' | 'hashtags'
|
|
|
|
text: string
|
|
|
|
offset: number
|
|
|
|
}
|
|
|
|
| undefined
|
2020-11-21 00:40:55 +01:00
|
|
|
emojis: Mastodon.Emoji[] | undefined
|
2020-11-17 23:57:23 +01:00
|
|
|
poll: {
|
|
|
|
active: boolean
|
|
|
|
total: number
|
|
|
|
options: {
|
|
|
|
'1': string
|
|
|
|
'2': string
|
|
|
|
'3': string
|
|
|
|
'4': string
|
|
|
|
[key: string]: string
|
|
|
|
}
|
|
|
|
multiple: boolean
|
|
|
|
expire:
|
|
|
|
| '300'
|
|
|
|
| '1800'
|
|
|
|
| '3600'
|
|
|
|
| '21600'
|
|
|
|
| '86400'
|
|
|
|
| '259200'
|
|
|
|
| '604800'
|
|
|
|
| string
|
|
|
|
}
|
2020-11-19 22:45:26 +01:00
|
|
|
attachments: {
|
|
|
|
id: string
|
|
|
|
url: string
|
|
|
|
preview_url: string
|
|
|
|
description: string
|
|
|
|
}[]
|
|
|
|
visibility: 'public' | 'unlisted' | 'private' | 'direct'
|
2020-11-15 22:33:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export type PostAction =
|
|
|
|
| {
|
|
|
|
type: 'text'
|
|
|
|
payload: Partial<PostState['text']>
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
type: 'selection'
|
|
|
|
payload: PostState['selection']
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
type: 'overlay'
|
|
|
|
payload: PostState['overlay']
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
type: 'tag'
|
|
|
|
payload: PostState['tag']
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
type: 'emojis'
|
|
|
|
payload: PostState['emojis']
|
|
|
|
}
|
2020-11-17 23:57:23 +01:00
|
|
|
| {
|
|
|
|
type: 'poll'
|
|
|
|
payload: PostState['poll']
|
|
|
|
}
|
2020-11-19 22:45:26 +01:00
|
|
|
| {
|
|
|
|
type: 'attachments/add'
|
|
|
|
payload: {
|
|
|
|
id: string
|
|
|
|
url: string
|
|
|
|
preview_url: string
|
|
|
|
description: string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
type: 'attachments/remove'
|
|
|
|
payload: {
|
|
|
|
id: string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
type: 'visibility'
|
|
|
|
payload: PostState['visibility']
|
|
|
|
}
|
2020-11-15 22:33:09 +01:00
|
|
|
|
|
|
|
const postInitialState: PostState = {
|
|
|
|
text: {
|
|
|
|
count: 0,
|
|
|
|
raw: '',
|
|
|
|
formatted: undefined
|
|
|
|
},
|
|
|
|
selection: { start: 0, end: 0 },
|
|
|
|
overlay: null,
|
|
|
|
tag: undefined,
|
2020-11-17 23:57:23 +01:00
|
|
|
emojis: undefined,
|
|
|
|
poll: {
|
|
|
|
active: false,
|
|
|
|
total: 2,
|
|
|
|
options: {
|
|
|
|
'1': '',
|
|
|
|
'2': '',
|
|
|
|
'3': '',
|
|
|
|
'4': ''
|
|
|
|
},
|
|
|
|
multiple: false,
|
|
|
|
expire: '86400'
|
2020-11-19 22:45:26 +01:00
|
|
|
},
|
|
|
|
attachments: [],
|
2020-11-21 00:40:55 +01:00
|
|
|
visibility:
|
|
|
|
getLocalAccountPreferences(store.getState())[
|
|
|
|
'posting:default:visibility'
|
|
|
|
] || 'public'
|
2020-11-15 22:33:09 +01:00
|
|
|
}
|
|
|
|
const postReducer = (state: PostState, action: PostAction): PostState => {
|
|
|
|
switch (action.type) {
|
|
|
|
case 'text':
|
|
|
|
return { ...state, text: { ...state.text, ...action.payload } }
|
|
|
|
case 'selection':
|
|
|
|
return { ...state, selection: action.payload }
|
|
|
|
case 'overlay':
|
|
|
|
return { ...state, overlay: action.payload }
|
|
|
|
case 'tag':
|
|
|
|
return { ...state, tag: action.payload }
|
|
|
|
case 'emojis':
|
|
|
|
return { ...state, emojis: action.payload }
|
2020-11-17 23:57:23 +01:00
|
|
|
case 'poll':
|
|
|
|
return { ...state, poll: action.payload }
|
2020-11-19 22:45:26 +01:00
|
|
|
case 'attachments/add':
|
|
|
|
return { ...state, attachments: state.attachments.concat(action.payload) }
|
|
|
|
case 'attachments/remove':
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
attachments: state.attachments.filter(a => a.id !== action.payload.id)
|
|
|
|
}
|
|
|
|
case 'visibility':
|
|
|
|
return { ...state, visibility: action.payload }
|
2020-11-15 22:33:09 +01:00
|
|
|
default:
|
|
|
|
throw new Error('Unexpected action')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-08 01:10:38 +01:00
|
|
|
const PostToot: React.FC = () => {
|
2020-11-08 19:13:46 +01:00
|
|
|
const navigation = useNavigation()
|
|
|
|
|
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-11-15 22:33:09 +01:00
|
|
|
const [postState, postDispatch] = useReducer(postReducer, postInitialState)
|
|
|
|
|
2020-11-17 23:57:23 +01:00
|
|
|
const tootPost = async () => {
|
|
|
|
if (postState.text.count < 0) {
|
|
|
|
Alert.alert('字数超限', '', [
|
|
|
|
{
|
|
|
|
text: '返回继续编辑'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
} else {
|
|
|
|
const formData = new FormData()
|
|
|
|
formData.append('status', postState.text.raw)
|
|
|
|
if (postState.poll.active) {
|
|
|
|
Object.values(postState.poll.options)
|
|
|
|
.filter(e => e.length)
|
|
|
|
.forEach(e => {
|
|
|
|
formData.append('poll[options][]', e)
|
|
|
|
})
|
|
|
|
formData.append('poll[expires_in]', postState.poll.expire)
|
|
|
|
formData.append('poll[multiple]', postState.poll.multiple.toString())
|
|
|
|
}
|
2020-11-20 01:41:46 +01:00
|
|
|
if (postState.attachments.length > 0) {
|
|
|
|
postState.attachments.forEach(attachment =>
|
|
|
|
formData.append('media_ids[]', attachment.id)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
formData.append('visibility', postState.visibility)
|
2020-11-17 23:57:23 +01:00
|
|
|
|
|
|
|
client({
|
|
|
|
method: 'post',
|
|
|
|
instance: 'local',
|
|
|
|
endpoint: 'statuses',
|
|
|
|
headers: {
|
|
|
|
'Idempotency-Key': Date.now().toString() + Math.random().toString()
|
|
|
|
},
|
|
|
|
body: formData
|
|
|
|
})
|
|
|
|
.then(
|
|
|
|
res => {
|
|
|
|
if (res.body.id) {
|
|
|
|
Alert.alert('发布成功', '', [
|
|
|
|
{
|
|
|
|
text: '好的',
|
2020-11-20 01:41:46 +01:00
|
|
|
onPress: () => {
|
|
|
|
// clear homepage cache
|
|
|
|
navigation.goBack()
|
|
|
|
}
|
2020-11-17 23:57:23 +01:00
|
|
|
}
|
|
|
|
])
|
|
|
|
} else {
|
|
|
|
Alert.alert('发布失败', '', [
|
|
|
|
{
|
|
|
|
text: '返回重试'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
}
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
Alert.alert('发布失败', error.body, [
|
|
|
|
{
|
|
|
|
text: '返回重试'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.catch(() => {
|
|
|
|
Alert.alert('发布失败', '', [
|
|
|
|
{
|
|
|
|
text: '返回重试'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-08 01:10:38 +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']}
|
|
|
|
>
|
|
|
|
<Stack.Navigator>
|
|
|
|
<Stack.Screen
|
|
|
|
name='PostMain'
|
|
|
|
options={{
|
|
|
|
headerLeft: () => (
|
|
|
|
<Pressable
|
|
|
|
onPress={() =>
|
|
|
|
Alert.alert('确认取消编辑?', '', [
|
|
|
|
{ text: '继续编辑', style: 'cancel' },
|
2020-11-15 22:33:09 +01:00
|
|
|
{
|
2020-11-15 23:33:01 +01:00
|
|
|
text: '退出编辑',
|
|
|
|
style: 'destructive',
|
2020-11-15 22:33:09 +01:00
|
|
|
onPress: () => navigation.goBack()
|
|
|
|
}
|
|
|
|
])
|
|
|
|
}
|
2020-11-15 23:33:01 +01:00
|
|
|
>
|
|
|
|
<Text>退出编辑</Text>
|
|
|
|
</Pressable>
|
|
|
|
),
|
|
|
|
headerCenter: () => <></>,
|
|
|
|
headerRight: () => (
|
2020-11-17 23:57:23 +01:00
|
|
|
<Pressable onPress={async () => tootPost()}>
|
2020-11-15 23:33:01 +01:00
|
|
|
<Text>发嘟嘟</Text>
|
|
|
|
</Pressable>
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{props => (
|
|
|
|
<PostMain postState={postState} postDispatch={postDispatch} />
|
|
|
|
)}
|
|
|
|
</Stack.Screen>
|
|
|
|
</Stack.Navigator>
|
|
|
|
</SafeAreaView>
|
|
|
|
</KeyboardAvoidingView>
|
2020-11-08 01:10:38 +01:00
|
|
|
)
|
|
|
|
}
|
2020-11-19 22:45:26 +01:00
|
|
|
// ;(PostMain as any).whyDidYouRender = true
|
2020-11-08 01:10:38 +01:00
|
|
|
|
|
|
|
export default PostToot
|