mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Spoiler is done
This commit is contained in:
@ -1,24 +1,41 @@
|
||||
import React, { ReactNode, useEffect, useReducer, useState } from 'react'
|
||||
import { Alert, Keyboard, KeyboardAvoidingView } from 'react-native'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
StyleSheet,
|
||||
Text
|
||||
} from 'react-native'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import sha256 from 'crypto-js/sha256'
|
||||
|
||||
import { store } from 'src/store'
|
||||
import ComposeRoot from './Compose/Root'
|
||||
import client from 'src/api/client'
|
||||
import { getLocalAccountPreferences } from 'src/utils/slices/instancesSlice'
|
||||
import { HeaderLeft, HeaderRight } from 'src/components/Header'
|
||||
import { StyleConstants } from 'src/utils/styles/constants'
|
||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
|
||||
const Stack = createNativeStackNavigator()
|
||||
|
||||
export type PostState = {
|
||||
spoiler: {
|
||||
active: boolean
|
||||
count: number
|
||||
raw: string
|
||||
formatted: ReactNode
|
||||
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'
|
||||
@ -57,12 +74,12 @@ export type PostState = {
|
||||
|
||||
export type PostAction =
|
||||
| {
|
||||
type: 'text'
|
||||
payload: Partial<PostState['text']>
|
||||
type: 'spoiler'
|
||||
payload: Partial<PostState['spoiler']>
|
||||
}
|
||||
| {
|
||||
type: 'selection'
|
||||
payload: PostState['selection']
|
||||
type: 'text'
|
||||
payload: Partial<PostState['text']>
|
||||
}
|
||||
| {
|
||||
type: 'tag'
|
||||
@ -94,22 +111,29 @@ export type PostAction =
|
||||
}
|
||||
|
||||
const postInitialState: PostState = {
|
||||
text: {
|
||||
count: 500,
|
||||
spoiler: {
|
||||
active: false,
|
||||
count: 0,
|
||||
raw: '',
|
||||
formatted: undefined
|
||||
formatted: undefined,
|
||||
selection: { start: 0, end: 0 }
|
||||
},
|
||||
text: {
|
||||
count: 0,
|
||||
raw: '',
|
||||
formatted: undefined,
|
||||
selection: { start: 0, end: 0 }
|
||||
},
|
||||
selection: { start: 0, end: 0 },
|
||||
tag: undefined,
|
||||
emoji: { active: false, emojis: undefined },
|
||||
poll: {
|
||||
active: false,
|
||||
total: 2,
|
||||
options: {
|
||||
'0': undefined,
|
||||
'1': undefined,
|
||||
'2': undefined,
|
||||
'3': undefined,
|
||||
'4': undefined
|
||||
'3': undefined
|
||||
},
|
||||
multiple: false,
|
||||
expire: '86400'
|
||||
@ -123,10 +147,10 @@ const postInitialState: PostState = {
|
||||
}
|
||||
const postReducer = (state: PostState, action: PostAction): PostState => {
|
||||
switch (action.type) {
|
||||
case 'spoiler':
|
||||
return { ...state, spoiler: { ...state.spoiler, ...action.payload } }
|
||||
case 'text':
|
||||
return { ...state, text: { ...state.text, ...action.payload } }
|
||||
case 'selection':
|
||||
return { ...state, selection: action.payload }
|
||||
case 'tag':
|
||||
return { ...state, tag: action.payload }
|
||||
case 'emoji':
|
||||
@ -152,8 +176,11 @@ const postReducer = (state: PostState, action: PostAction): PostState => {
|
||||
}
|
||||
|
||||
const Compose: React.FC = () => {
|
||||
const { theme } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const [hasKeyboard, setHasKeyboard] = useState(false)
|
||||
useEffect(() => {
|
||||
Keyboard.addListener('keyboardWillShow', _keyboardDidShow)
|
||||
@ -175,6 +202,7 @@ const Compose: React.FC = () => {
|
||||
const [postState, postDispatch] = useReducer(postReducer, postInitialState)
|
||||
|
||||
const tootPost = async () => {
|
||||
setIsSubmitting(true)
|
||||
if (postState.text.count < 0) {
|
||||
Alert.alert('字数超限', '', [
|
||||
{
|
||||
@ -184,6 +212,10 @@ const Compose: React.FC = () => {
|
||||
} else {
|
||||
const formData = new FormData()
|
||||
|
||||
if (postState.spoiler.active) {
|
||||
formData.append('spoiler_text', postState.spoiler.raw)
|
||||
}
|
||||
|
||||
formData.append('status', postState.text.raw)
|
||||
|
||||
if (postState.poll.active) {
|
||||
@ -207,13 +239,25 @@ const Compose: React.FC = () => {
|
||||
instance: 'local',
|
||||
url: 'statuses',
|
||||
headers: {
|
||||
'Idempotency-Key': Date.now().toString() + Math.random().toString()
|
||||
'Idempotency-Key': sha256(
|
||||
postState.spoiler.raw +
|
||||
postState.text.raw +
|
||||
postState.poll.options['0'] +
|
||||
postState.poll.options['1'] +
|
||||
postState.poll.options['2'] +
|
||||
postState.poll.options['3'] +
|
||||
postState.poll.multiple +
|
||||
postState.poll.expire +
|
||||
postState.attachments.map(attachment => attachment.id) +
|
||||
postState.visibility
|
||||
).toString()
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(
|
||||
res => {
|
||||
if (res.body.id) {
|
||||
setIsSubmitting(false)
|
||||
Alert.alert('发布成功', '', [
|
||||
{
|
||||
text: '好的',
|
||||
@ -224,6 +268,7 @@ const Compose: React.FC = () => {
|
||||
}
|
||||
])
|
||||
} else {
|
||||
setIsSubmitting(false)
|
||||
Alert.alert('发布失败', '', [
|
||||
{
|
||||
text: '返回重试'
|
||||
@ -232,6 +277,7 @@ const Compose: React.FC = () => {
|
||||
}
|
||||
},
|
||||
error => {
|
||||
setIsSubmitting(false)
|
||||
Alert.alert('发布失败', error.body, [
|
||||
{
|
||||
text: '返回重试'
|
||||
@ -240,6 +286,7 @@ const Compose: React.FC = () => {
|
||||
}
|
||||
)
|
||||
.catch(() => {
|
||||
setIsSubmitting(false)
|
||||
Alert.alert('发布失败', '', [
|
||||
{
|
||||
text: '返回重试'
|
||||
@ -249,6 +296,10 @@ const Compose: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const totalTextCount =
|
||||
(postState.spoiler.active ? postState.spoiler.count : 0) +
|
||||
postState.text.count
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
||||
<SafeAreaView
|
||||
@ -274,16 +325,31 @@ const Compose: React.FC = () => {
|
||||
text='退出编辑'
|
||||
/>
|
||||
),
|
||||
headerCenter: () => <></>,
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
onPress={async () => tootPost()}
|
||||
text='发嘟嘟'
|
||||
disabled={
|
||||
postState.text.raw.length < 1 || postState.text.count < 0
|
||||
}
|
||||
/>
|
||||
)
|
||||
headerCenter: () => (
|
||||
<Text
|
||||
style={[
|
||||
styles.count,
|
||||
{
|
||||
color:
|
||||
totalTextCount > 500 ? theme.error : theme.secondary
|
||||
}
|
||||
]}
|
||||
>
|
||||
{totalTextCount} / 500
|
||||
</Text>
|
||||
),
|
||||
headerRight: () =>
|
||||
isSubmitting ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<HeaderRight
|
||||
onPress={async () => tootPost()}
|
||||
text='发嘟嘟'
|
||||
disabled={
|
||||
postState.text.raw.length < 1 || totalTextCount > 500
|
||||
}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
>
|
||||
{() => (
|
||||
@ -296,4 +362,11 @@ const Compose: React.FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
count: {
|
||||
textAlign: 'center',
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
}
|
||||
})
|
||||
|
||||
export default Compose
|
||||
|
Reference in New Issue
Block a user