import { useNavigation } from '@react-navigation/native' import React, { Dispatch, useCallback, useEffect, useRef, useState } from 'react' import { Alert, Animated, Dimensions, Image, KeyboardAvoidingView, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import Svg, { Circle, G, Path } from 'react-native-svg' import { createNativeStackNavigator } from 'react-native-screens/native-stack' import { HeaderLeft, HeaderRight } from '@components/Header' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import { PanGestureHandler } from 'react-native-gesture-handler' import { PostAction } from '@screens/Shared/Compose' import client from '@api/client' import AttachmentVideo from '@components/Timelines/Timeline/Shared/Attachment/AttachmentVideo' const Stack = createNativeStackNavigator() export interface Props { route: { params: { attachment: Mastodon.Attachment & { local_url: string } composeDispatch: Dispatch } } } const ComposeEditAttachment: React.FC = ({ route: { params: { attachment, composeDispatch } } }) => { const navigation = useNavigation() const { theme } = useTheme() const [altText, setAltText] = useState( attachment.description ) const focus = useRef({ x: 0, y: 0 }) useEffect(() => { const unsubscribe = navigation.addListener('beforeRemove', () => { let needUpdate = false if (altText) { attachment.description = altText needUpdate = true } if (attachment.type === 'image') { if (focus.current.x !== 0 || focus.current.y !== 0) { attachment.meta!.focus = { x: focus.current.x > 1 ? 1 : focus.current.x, y: focus.current.y > 1 ? 1 : focus.current.y } needUpdate = true } } if (needUpdate) { composeDispatch({ type: 'attachmentEdit', payload: attachment }) } }) return unsubscribe }, [navigation, altText]) const videoPlayback = useCallback(() => { return ( ) }, []) const imageFocus = useCallback(() => { const imageDimensionis = { width: Dimensions.get('screen').width, height: Dimensions.get('screen').width / (attachment as Mastodon.AttachmentImage).meta?.original?.aspect! } const panFocus = useRef( new Animated.ValueXY( (attachment as Mastodon.AttachmentImage).meta?.focus?.x && (attachment as Mastodon.AttachmentImage).meta?.focus?.y ? { x: ((attachment as Mastodon.AttachmentImage).meta!.focus!.x * imageDimensionis.width) / 2, y: (-(attachment as Mastodon.AttachmentImage).meta!.focus!.y * imageDimensionis.height) / 2 } : { x: 0, y: 0 } ) ).current const panX = panFocus.x.interpolate({ inputRange: [-imageDimensionis.width / 2, imageDimensionis.width / 2], outputRange: [-imageDimensionis.width / 2, imageDimensionis.width / 2], extrapolate: 'clamp' }) const panY = panFocus.y.interpolate({ inputRange: [-imageDimensionis.height / 2, imageDimensionis.height / 2], outputRange: [-imageDimensionis.height / 2, imageDimensionis.height / 2], extrapolate: 'clamp' }) panFocus.addListener(e => { focus.current = { x: e.x / (imageDimensionis.width / 2), y: -e.y / (imageDimensionis.height / 2) } }) const handleGesture = Animated.event( [{ nativeEvent: { translationX: panFocus.x, translationY: panFocus.y } }], { useNativeDriver: true } ) const onHandlerStateChange = useCallback(() => { panFocus.extractOffset() }, []) return ( <> 在预览图上拖动圆圈,以选择缩略图的焦点。 ) }, []) const altTextInput = useCallback(() => { return ( 为附件添加文字说明 setAltText(e)} placeholder={ '你可以为附件添加文字说明,以便更多人可以查看他们(包括视力障碍或视力受损人士)。\n\n优质的描述应该简洁明了,但要准确地描述照片中的内容,以便用户理解其含义。' } placeholderTextColor={theme.secondary} scrollEnabled value={altText} /> {altText?.length || 0} / 1500 ) }, [altText]) return ( ( navigation.goBack()} /> ), headerRight: () => ( { const formData = new FormData() if (altText) { formData.append('description', altText) } if (focus.current.x !== 0 || focus.current.y !== 0) { formData.append( 'focus', `${focus.current.x},${focus.current.y}` ) } client({ method: 'put', instance: 'local', url: `media/${attachment.id}`, ...(formData && { body: formData }) }) .then( res => { if (res.body.id === attachment.id) { Alert.alert('修改成功', '', [ { text: '好的', onPress: () => { navigation.goBack() } } ]) } else { Alert.alert('修改失败', '', [ { text: '返回重试' } ]) } }, error => { Alert.alert('修改失败', error.body, [ { text: '返回重试' } ]) } ) .catch(() => { Alert.alert('修改失败', '', [ { text: '返回重试' } ]) }) }} /> ) }} > {() => { switch (attachment.type) { case 'image': return ( {imageFocus()} {altTextInput()} ) case 'video': case 'gifv': return ( {videoPlayback()} {altTextInput()} ) } return null }} ) } const styles = StyleSheet.create({ imageFocusText: { fontSize: StyleConstants.Font.Size.M, padding: StyleConstants.Spacing.Global.PagePadding }, altTextContainer: { padding: StyleConstants.Spacing.Global.PagePadding }, altTextInputHeading: { fontSize: StyleConstants.Font.Size.M, fontWeight: StyleConstants.Font.Weight.Bold }, altTextInput: { height: 200, fontSize: StyleConstants.Font.Size.M, marginTop: StyleConstants.Spacing.M, marginBottom: StyleConstants.Spacing.S, padding: StyleConstants.Spacing.Global.PagePadding, paddingTop: StyleConstants.Spacing.S * 1.5, borderWidth: StyleSheet.hairlineWidth }, altTextLength: { textAlign: 'right', marginRight: StyleConstants.Spacing.S, fontSize: StyleConstants.Font.Size.S, marginBottom: StyleConstants.Spacing.M } }) export default ComposeEditAttachment