mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Fixed #548
This commit is contained in:
		| @@ -407,12 +407,12 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({ | ||||
|           <Stack.Screen | ||||
|             name='Screen-Compose-DraftsList' | ||||
|             component={ComposeDraftsList} | ||||
|             options={{ headerShown: false, presentation: 'modal' }} | ||||
|             options={{ presentation: 'modal' }} | ||||
|           /> | ||||
|           <Stack.Screen | ||||
|             name='Screen-Compose-EditAttachment' | ||||
|             component={ComposeEditAttachment} | ||||
|             options={{ headerShown: false, presentation: 'modal' }} | ||||
|             options={{ presentation: 'modal' }} | ||||
|           /> | ||||
|         </Stack.Navigator> | ||||
|       </ComposeContext.Provider> | ||||
|   | ||||
| @@ -1,49 +1,227 @@ | ||||
| import apiInstance from '@api/instance' | ||||
| import { HeaderLeft } from '@components/Header' | ||||
| import { createNativeStackNavigator } from '@react-navigation/native-stack' | ||||
| import Icon from '@components/Icon' | ||||
| import ComponentSeparator from '@components/Separator' | ||||
| import CustomText from '@components/Text' | ||||
| import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created' | ||||
| import { useAppDispatch } from '@root/store' | ||||
| import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators' | ||||
| import React, { useCallback } from 'react' | ||||
| import { getInstanceDrafts, removeInstanceDraft } from '@utils/slices/instancesSlice' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import React, { useContext, useEffect, useState } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import ComposeDraftsListRoot from './DraftsList/Root' | ||||
| import { Dimensions, Modal, Platform, Pressable, View } from 'react-native' | ||||
| import FastImage from 'react-native-fast-image' | ||||
| import { PanGestureHandler } from 'react-native-gesture-handler' | ||||
| import { SwipeListView } from 'react-native-swipe-list-view' | ||||
| import { useSelector } from 'react-redux' | ||||
| import ComposeContext from './utils/createContext' | ||||
| import { formatText } from './utils/processText' | ||||
| import { ComposeStateDraft, ExtendedAttachment } from './utils/types' | ||||
|  | ||||
| const Stack = createNativeStackNavigator() | ||||
|  | ||||
| const ComposeDraftsList: React.FC< | ||||
|   ScreenComposeStackScreenProps<'Screen-Compose-DraftsList'> | ||||
| > = ({ | ||||
| const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-DraftsList'>> = ({ | ||||
|   navigation, | ||||
|   route: { | ||||
|     params: { timestamp } | ||||
|   }, | ||||
|   navigation | ||||
|   } | ||||
| }) => { | ||||
|   const { colors } = useTheme() | ||||
|   const { t } = useTranslation('screenCompose') | ||||
|  | ||||
|   const children = useCallback( | ||||
|     () => <ComposeDraftsListRoot timestamp={timestamp} />, | ||||
|     [] | ||||
|   ) | ||||
|   const headerLeft = useCallback( | ||||
|     () => ( | ||||
|       <HeaderLeft | ||||
|         type='icon' | ||||
|         content='ChevronDown' | ||||
|         onPress={() => navigation.goBack()} | ||||
|       /> | ||||
|     ), | ||||
|     [] | ||||
|   useEffect(() => { | ||||
|     navigation.setOptions({ | ||||
|       title: t('content.draftsList.header.title'), | ||||
|       headerLeft: () => ( | ||||
|         <HeaderLeft type='icon' content='ChevronDown' onPress={() => navigation.goBack()} /> | ||||
|       ) | ||||
|     }) | ||||
|   }, []) | ||||
|  | ||||
|   const { composeDispatch } = useContext(ComposeContext) | ||||
|   const instanceDrafts = useSelector(getInstanceDrafts)?.filter( | ||||
|     draft => draft.timestamp !== timestamp | ||||
|   ) | ||||
|   const [checkingAttachments, setCheckingAttachments] = useState(false) | ||||
|   const dispatch = useAppDispatch() | ||||
|  | ||||
|   const actionWidth = StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4 | ||||
|  | ||||
|   return ( | ||||
|     <Stack.Navigator> | ||||
|       <Stack.Screen | ||||
|         name='Screen-Compose-EditAttachment-Root' | ||||
|         children={children} | ||||
|         options={{ | ||||
|           headerLeft, | ||||
|           title: t('content.draftsList.header.title'), | ||||
|           headerShadowVisible: false | ||||
|     <> | ||||
|       <View | ||||
|         style={{ | ||||
|           flexDirection: 'row', | ||||
|           alignItems: 'center', | ||||
|           marginHorizontal: StyleConstants.Spacing.Global.PagePadding, | ||||
|           padding: StyleConstants.Spacing.S, | ||||
|           borderColor: colors.border, | ||||
|           borderWidth: 1, | ||||
|           borderRadius: StyleConstants.Spacing.S | ||||
|         }} | ||||
|       > | ||||
|         <Icon | ||||
|           name='AlertTriangle' | ||||
|           color={colors.secondary} | ||||
|           size={StyleConstants.Font.Size.M} | ||||
|           style={{ marginRight: StyleConstants.Spacing.S }} | ||||
|         /> | ||||
|         <CustomText fontStyle='S' style={{ flexShrink: 1, color: colors.secondary }}> | ||||
|           {t('content.draftsList.warning')} | ||||
|         </CustomText> | ||||
|       </View> | ||||
|       <PanGestureHandler enabled={Platform.OS === 'ios'}> | ||||
|         <SwipeListView | ||||
|           data={instanceDrafts} | ||||
|           renderItem={({ item }: { item: ComposeStateDraft }) => { | ||||
|             return ( | ||||
|               <Pressable | ||||
|                 accessibilityHint={t('content.draftsList.content.accessibilityHint')} | ||||
|                 style={{ | ||||
|                   flex: 1, | ||||
|                   padding: StyleConstants.Spacing.Global.PagePadding, | ||||
|                   backgroundColor: colors.backgroundDefault | ||||
|                 }} | ||||
|                 onPress={async () => { | ||||
|                   setCheckingAttachments(true) | ||||
|                   let tempDraft = item | ||||
|                   let tempUploads: ExtendedAttachment[] = [] | ||||
|                   if (item.attachments && item.attachments.uploads.length) { | ||||
|                     for (const attachment of item.attachments.uploads) { | ||||
|                       await apiInstance<Mastodon.Attachment>({ | ||||
|                         method: 'get', | ||||
|                         url: `media/${attachment.remote?.id}` | ||||
|                       }) | ||||
|                         .then(res => { | ||||
|                           if (res.body.id === attachment.remote?.id) { | ||||
|                             tempUploads.push(attachment) | ||||
|                           } | ||||
|                         }) | ||||
|                         .catch(() => {}) | ||||
|                     } | ||||
|                     tempDraft = { | ||||
|                       ...tempDraft, | ||||
|                       attachments: { ...item.attachments, uploads: tempUploads } | ||||
|                     } | ||||
|                   } | ||||
|  | ||||
|                   tempDraft.spoiler?.length && | ||||
|                     formatText({ textInput: 'text', composeDispatch, content: tempDraft.spoiler }) | ||||
|                   tempDraft.text?.length && | ||||
|                     formatText({ textInput: 'text', composeDispatch, content: tempDraft.text }) | ||||
|                   composeDispatch({ | ||||
|                     type: 'loadDraft', | ||||
|                     payload: tempDraft | ||||
|                   }) | ||||
|                   dispatch(removeInstanceDraft(item.timestamp)) | ||||
|                   navigation.goBack() | ||||
|                 }} | ||||
|               > | ||||
|                 <View style={{ flex: 1 }}> | ||||
|                   <HeaderSharedCreated created_at={item.timestamp} /> | ||||
|                   <CustomText | ||||
|                     fontStyle='M' | ||||
|                     numberOfLines={2} | ||||
|                     style={{ | ||||
|                       marginTop: StyleConstants.Spacing.XS, | ||||
|                       color: colors.primaryDefault | ||||
|                     }} | ||||
|                   > | ||||
|                     {item.text || item.spoiler || t('content.draftsList.content.textEmpty')} | ||||
|                   </CustomText> | ||||
|                   {item.attachments?.uploads.length ? ( | ||||
|                     <View | ||||
|                       style={{ | ||||
|                         flex: 1, | ||||
|                         flexDirection: 'row', | ||||
|                         marginTop: StyleConstants.Spacing.S | ||||
|                       }} | ||||
|                     > | ||||
|                       {item.attachments.uploads.map((attachment, index) => ( | ||||
|                         <FastImage | ||||
|                           key={index} | ||||
|                           style={{ | ||||
|                             width: | ||||
|                               (Dimensions.get('screen').width - | ||||
|                                 StyleConstants.Spacing.Global.PagePadding * 2 - | ||||
|                                 StyleConstants.Spacing.S * 3) / | ||||
|                               4, | ||||
|                             height: | ||||
|                               (Dimensions.get('screen').width - | ||||
|                                 StyleConstants.Spacing.Global.PagePadding * 2 - | ||||
|                                 StyleConstants.Spacing.S * 3) / | ||||
|                               4, | ||||
|                             marginLeft: index !== 0 ? StyleConstants.Spacing.S : 0 | ||||
|                           }} | ||||
|                           source={{ | ||||
|                             uri: attachment.local?.thumbnail || attachment.remote?.preview_url | ||||
|                           }} | ||||
|                         /> | ||||
|                       ))} | ||||
|                     </View> | ||||
|                   ) : null} | ||||
|                 </View> | ||||
|               </Pressable> | ||||
|             ) | ||||
|           }} | ||||
|           renderHiddenItem={({ item }) => ( | ||||
|             <Pressable | ||||
|               style={{ | ||||
|                 flex: 1, | ||||
|                 flexDirection: 'row', | ||||
|                 justifyContent: 'flex-end', | ||||
|                 backgroundColor: colors.red | ||||
|               }} | ||||
|               onPress={() => dispatch(removeInstanceDraft(item.timestamp))} | ||||
|               children={ | ||||
|                 <View | ||||
|                   style={{ | ||||
|                     flexBasis: | ||||
|                       StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4, | ||||
|                     justifyContent: 'center', | ||||
|                     alignItems: 'center' | ||||
|                   }} | ||||
|                   children={ | ||||
|                     <Icon | ||||
|                       name='Trash' | ||||
|                       size={StyleConstants.Font.Size.L} | ||||
|                       color={colors.primaryOverlay} | ||||
|                     /> | ||||
|                   } | ||||
|                 /> | ||||
|               } | ||||
|             /> | ||||
|           )} | ||||
|           disableRightSwipe={true} | ||||
|           rightOpenValue={-actionWidth} | ||||
|           previewOpenValue={-actionWidth / 2} | ||||
|           ItemSeparatorComponent={ComponentSeparator} | ||||
|           keyExtractor={item => item.timestamp.toString()} | ||||
|         /> | ||||
|       </PanGestureHandler> | ||||
|       <Modal | ||||
|         transparent | ||||
|         animationType='fade' | ||||
|         visible={checkingAttachments} | ||||
|         children={ | ||||
|           <View | ||||
|             style={{ | ||||
|               flex: 1, | ||||
|               justifyContent: 'center', | ||||
|               alignItems: 'center', | ||||
|               backgroundColor: colors.backgroundOverlayInvert | ||||
|             }} | ||||
|             children={ | ||||
|               <CustomText | ||||
|                 fontStyle='M' | ||||
|                 children={t('content.draftsList.checkAttachment')} | ||||
|                 style={{ color: colors.primaryOverlay }} | ||||
|               /> | ||||
|             } | ||||
|           /> | ||||
|         } | ||||
|       /> | ||||
|     </Stack.Navigator> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,223 +0,0 @@ | ||||
| import apiInstance from '@api/instance' | ||||
| import Icon from '@components/Icon' | ||||
| import ComponentSeparator from '@components/Separator' | ||||
| import CustomText from '@components/Text' | ||||
| import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created' | ||||
| import { useNavigation } from '@react-navigation/native' | ||||
| import { useAppDispatch } from '@root/store' | ||||
| import { getInstanceDrafts, removeInstanceDraft } from '@utils/slices/instancesSlice' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import React, { useCallback, useContext, useState } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { Dimensions, Image, Modal, Platform, Pressable, View } from 'react-native' | ||||
| import { PanGestureHandler } from 'react-native-gesture-handler' | ||||
| import { SwipeListView } from 'react-native-swipe-list-view' | ||||
| import { useSelector } from 'react-redux' | ||||
| import ComposeContext from '../utils/createContext' | ||||
| import { formatText } from '../utils/processText' | ||||
| import { ComposeStateDraft, ExtendedAttachment } from '../utils/types' | ||||
|  | ||||
| export interface Props { | ||||
|   timestamp: number | ||||
| } | ||||
|  | ||||
| const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => { | ||||
|   const { composeDispatch } = useContext(ComposeContext) | ||||
|   const { t } = useTranslation('screenCompose') | ||||
|   const navigation = useNavigation() | ||||
|   const dispatch = useAppDispatch() | ||||
|   const { colors, theme } = useTheme() | ||||
|   const instanceDrafts = useSelector(getInstanceDrafts)?.filter( | ||||
|     draft => draft.timestamp !== timestamp | ||||
|   ) | ||||
|  | ||||
|   const actionWidth = StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4 | ||||
|  | ||||
|   const [checkingAttachments, setCheckingAttachments] = useState(false) | ||||
|  | ||||
|   const renderItem = useCallback( | ||||
|     ({ item }: { item: ComposeStateDraft }) => { | ||||
|       return ( | ||||
|         <Pressable | ||||
|           accessibilityHint={t('content.draftsList.content.accessibilityHint')} | ||||
|           style={{ | ||||
|             flex: 1, | ||||
|             padding: StyleConstants.Spacing.Global.PagePadding, | ||||
|             backgroundColor: colors.backgroundDefault | ||||
|           }} | ||||
|           onPress={async () => { | ||||
|             setCheckingAttachments(true) | ||||
|             let tempDraft = item | ||||
|             let tempUploads: ExtendedAttachment[] = [] | ||||
|             if (item.attachments && item.attachments.uploads.length) { | ||||
|               for (const attachment of item.attachments.uploads) { | ||||
|                 await apiInstance<Mastodon.Attachment>({ | ||||
|                   method: 'get', | ||||
|                   url: `media/${attachment.remote?.id}` | ||||
|                 }) | ||||
|                   .then(res => { | ||||
|                     if (res.body.id === attachment.remote?.id) { | ||||
|                       tempUploads.push(attachment) | ||||
|                     } | ||||
|                   }) | ||||
|                   .catch(() => {}) | ||||
|               } | ||||
|               tempDraft = { | ||||
|                 ...tempDraft, | ||||
|                 attachments: { ...item.attachments, uploads: tempUploads } | ||||
|               } | ||||
|             } | ||||
|  | ||||
|             tempDraft.spoiler?.length && | ||||
|               formatText({ textInput: 'text', composeDispatch, content: tempDraft.spoiler }) | ||||
|             tempDraft.text?.length && | ||||
|               formatText({ textInput: 'text', composeDispatch, content: tempDraft.text }) | ||||
|             composeDispatch({ | ||||
|               type: 'loadDraft', | ||||
|               payload: tempDraft | ||||
|             }) | ||||
|             dispatch(removeInstanceDraft(item.timestamp)) | ||||
|             navigation.goBack() | ||||
|           }} | ||||
|         > | ||||
|           <View style={{ flex: 1 }}> | ||||
|             <HeaderSharedCreated created_at={item.timestamp} /> | ||||
|             <CustomText | ||||
|               fontStyle='M' | ||||
|               numberOfLines={2} | ||||
|               style={{ | ||||
|                 marginTop: StyleConstants.Spacing.XS, | ||||
|                 color: colors.primaryDefault | ||||
|               }} | ||||
|             > | ||||
|               {item.text || item.spoiler || t('content.draftsList.content.textEmpty')} | ||||
|             </CustomText> | ||||
|             {item.attachments?.uploads.length ? ( | ||||
|               <View | ||||
|                 style={{ | ||||
|                   flex: 1, | ||||
|                   flexDirection: 'row', | ||||
|                   marginTop: StyleConstants.Spacing.S | ||||
|                 }} | ||||
|               > | ||||
|                 {item.attachments.uploads.map((attachment, index) => ( | ||||
|                   <Image | ||||
|                     key={index} | ||||
|                     style={{ | ||||
|                       width: | ||||
|                         (Dimensions.get('screen').width - | ||||
|                           StyleConstants.Spacing.Global.PagePadding * 2 - | ||||
|                           StyleConstants.Spacing.S * 3) / | ||||
|                         4, | ||||
|                       height: | ||||
|                         (Dimensions.get('screen').width - | ||||
|                           StyleConstants.Spacing.Global.PagePadding * 2 - | ||||
|                           StyleConstants.Spacing.S * 3) / | ||||
|                         4, | ||||
|                       marginLeft: index !== 0 ? StyleConstants.Spacing.S : 0 | ||||
|                     }} | ||||
|                     source={{ | ||||
|                       uri: attachment.local?.thumbnail || attachment.remote?.preview_url | ||||
|                     }} | ||||
|                   /> | ||||
|                 ))} | ||||
|               </View> | ||||
|             ) : null} | ||||
|           </View> | ||||
|         </Pressable> | ||||
|       ) | ||||
|     }, | ||||
|     [theme] | ||||
|   ) | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <View | ||||
|         style={{ | ||||
|           flexDirection: 'row', | ||||
|           alignItems: 'center', | ||||
|           marginHorizontal: StyleConstants.Spacing.Global.PagePadding, | ||||
|           padding: StyleConstants.Spacing.S, | ||||
|           borderColor: colors.border, | ||||
|           borderWidth: 1, | ||||
|           borderRadius: StyleConstants.Spacing.S | ||||
|         }} | ||||
|       > | ||||
|         <Icon | ||||
|           name='AlertTriangle' | ||||
|           color={colors.secondary} | ||||
|           size={StyleConstants.Font.Size.M} | ||||
|           style={{ marginRight: StyleConstants.Spacing.S }} | ||||
|         /> | ||||
|         <CustomText fontStyle='S' style={{ flexShrink: 1, color: colors.secondary }}> | ||||
|           {t('content.draftsList.warning')} | ||||
|         </CustomText> | ||||
|       </View> | ||||
|       <PanGestureHandler enabled={Platform.OS === 'ios'}> | ||||
|         <SwipeListView | ||||
|           data={instanceDrafts} | ||||
|           renderItem={renderItem} | ||||
|           renderHiddenItem={({ item }) => ( | ||||
|             <Pressable | ||||
|               style={{ | ||||
|                 flex: 1, | ||||
|                 flexDirection: 'row', | ||||
|                 justifyContent: 'flex-end', | ||||
|                 backgroundColor: colors.red | ||||
|               }} | ||||
|               onPress={() => dispatch(removeInstanceDraft(item.timestamp))} | ||||
|               children={ | ||||
|                 <View | ||||
|                   style={{ | ||||
|                     flexBasis: | ||||
|                       StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4, | ||||
|                     justifyContent: 'center', | ||||
|                     alignItems: 'center', | ||||
|                     backgroundColor: 'rgba(0, 255, 0, 0.2)' | ||||
|                   }} | ||||
|                   children={ | ||||
|                     <Icon | ||||
|                       name='Trash' | ||||
|                       size={StyleConstants.Font.Size.L} | ||||
|                       color={colors.primaryOverlay} | ||||
|                     /> | ||||
|                   } | ||||
|                 /> | ||||
|               } | ||||
|             /> | ||||
|           )} | ||||
|           disableRightSwipe={true} | ||||
|           rightOpenValue={-actionWidth} | ||||
|           previewOpenValue={-actionWidth / 2} | ||||
|           ItemSeparatorComponent={ComponentSeparator} | ||||
|           keyExtractor={item => item.timestamp.toString()} | ||||
|         /> | ||||
|       </PanGestureHandler> | ||||
|       <Modal | ||||
|         transparent | ||||
|         animationType='fade' | ||||
|         visible={checkingAttachments} | ||||
|         children={ | ||||
|           <View | ||||
|             style={{ | ||||
|               flex: 1, | ||||
|               justifyContent: 'center', | ||||
|               alignItems: 'center', | ||||
|               backgroundColor: colors.backgroundOverlayInvert | ||||
|             }} | ||||
|             children={ | ||||
|               <CustomText | ||||
|                 fontStyle='M' | ||||
|                 children={t('content.draftsList.checkAttachment')} | ||||
|                 style={{ color: colors.primaryOverlay }} | ||||
|               /> | ||||
|             } | ||||
|           /> | ||||
|         } | ||||
|       /> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default ComposeDraftsListRoot | ||||
| @@ -1,49 +1,90 @@ | ||||
| import { HeaderLeft } from '@components/Header' | ||||
| import { createNativeStackNavigator } from '@react-navigation/native-stack' | ||||
| import apiInstance from '@api/instance' | ||||
| import haptics from '@components/haptics' | ||||
| import { HeaderLeft, HeaderRight } from '@components/Header' | ||||
| import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators' | ||||
| import React from 'react' | ||||
| import React, { useContext, useEffect, useState } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { KeyboardAvoidingView, Platform } from 'react-native' | ||||
| import { Alert, KeyboardAvoidingView, Platform } from 'react-native' | ||||
| import { SafeAreaView } from 'react-native-safe-area-context' | ||||
| import ComposeEditAttachmentRoot from './EditAttachment/Root' | ||||
| import ComposeEditAttachmentSubmit from './EditAttachment/Submit' | ||||
| import ComposeContext from './utils/createContext' | ||||
|  | ||||
| const Stack = createNativeStackNavigator() | ||||
|  | ||||
| const ComposeEditAttachment: React.FC<ScreenComposeStackScreenProps< | ||||
|   'Screen-Compose-EditAttachment' | ||||
| >> = ({ | ||||
| const ComposeEditAttachment: React.FC< | ||||
|   ScreenComposeStackScreenProps<'Screen-Compose-EditAttachment'> | ||||
| > = ({ | ||||
|   navigation, | ||||
|   route: { | ||||
|     params: { index } | ||||
|   }, | ||||
|   navigation | ||||
| }) => { | ||||
|     const { t } = useTranslation('screenCompose') | ||||
|  | ||||
|     return ( | ||||
|       <KeyboardAvoidingView | ||||
|         behavior={Platform.OS === 'ios' ? 'padding' : 'height'} | ||||
|         style={{ flex: 1 }} | ||||
|       > | ||||
|         <SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}> | ||||
|           <Stack.Navigator> | ||||
|             <Stack.Screen | ||||
|               name='Screen-Compose-EditAttachment-Root' | ||||
|               children={() => <ComposeEditAttachmentRoot index={index} />} | ||||
|               options={{ | ||||
|                 headerLeft: () => <HeaderLeft | ||||
|                   type='icon' | ||||
|                   content='ChevronDown' | ||||
|                   onPress={() => navigation.goBack()} | ||||
|                 />, | ||||
|                 headerRight: () => <ComposeEditAttachmentSubmit index={index} />, | ||||
|                 title: t('content.editAttachment.header.title') | ||||
|               }} | ||||
|             /> | ||||
|           </Stack.Navigator> | ||||
|         </SafeAreaView> | ||||
|       </KeyboardAvoidingView> | ||||
|     ) | ||||
|   } | ||||
| }) => { | ||||
|   const { t } = useTranslation('screenCompose') | ||||
|  | ||||
|   const { composeState } = useContext(ComposeContext) | ||||
|   const [isSubmitting, setIsSubmitting] = useState(false) | ||||
|  | ||||
|   const theAttachment = composeState.attachments.uploads[index].remote! | ||||
|  | ||||
|   useEffect(() => { | ||||
|     navigation.setOptions({ | ||||
|       title: t('content.editAttachment.header.title'), | ||||
|       headerLeft: () => ( | ||||
|         <HeaderLeft type='icon' content='ChevronDown' onPress={() => navigation.goBack()} /> | ||||
|       ), | ||||
|       headerRight: () => ( | ||||
|         <HeaderRight | ||||
|           accessibilityLabel={t('content.editAttachment.header.right.accessibilityLabel')} | ||||
|           type='icon' | ||||
|           content='Save' | ||||
|           loading={isSubmitting} | ||||
|           onPress={() => { | ||||
|             setIsSubmitting(true) | ||||
|             const formData = new FormData() | ||||
|             if (theAttachment.description) { | ||||
|               formData.append('description', theAttachment.description) | ||||
|             } | ||||
|             if (theAttachment.meta?.focus?.x !== 0 || theAttachment.meta.focus.y !== 0) { | ||||
|               formData.append( | ||||
|                 'focus', | ||||
|                 `${theAttachment.meta?.focus?.x || 0},${-theAttachment.meta?.focus?.y || 0}` | ||||
|               ) | ||||
|             } | ||||
|  | ||||
|             theAttachment?.id && | ||||
|               apiInstance<Mastodon.Attachment>({ | ||||
|                 method: 'put', | ||||
|                 url: `media/${theAttachment.id}`, | ||||
|                 body: formData | ||||
|               }) | ||||
|                 .then(() => { | ||||
|                   haptics('Success') | ||||
|                   navigation.goBack() | ||||
|                 }) | ||||
|                 .catch(() => { | ||||
|                   setIsSubmitting(false) | ||||
|                   haptics('Error') | ||||
|                   Alert.alert(t('content.editAttachment.header.right.failed.title'), undefined, [ | ||||
|                     { | ||||
|                       text: t('content.editAttachment.header.right.failed.button'), | ||||
|                       style: 'cancel' | ||||
|                     } | ||||
|                   ]) | ||||
|                 }) | ||||
|           }} | ||||
|         /> | ||||
|       ) | ||||
|     }) | ||||
|   }, [theAttachment]) | ||||
|  | ||||
|   return ( | ||||
|     <KeyboardAvoidingView | ||||
|       behavior={Platform.OS === 'ios' ? 'padding' : 'height'} | ||||
|       style={{ flex: 1 }} | ||||
|     > | ||||
|       <SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}> | ||||
|         <ComposeEditAttachmentRoot index={index} /> | ||||
|       </SafeAreaView> | ||||
|     </KeyboardAvoidingView> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default ComposeEditAttachment | ||||
|   | ||||
| @@ -1,79 +0,0 @@ | ||||
| import apiInstance from '@api/instance' | ||||
| import haptics from '@components/haptics' | ||||
| import { HeaderRight } from '@components/Header' | ||||
| import { useNavigation } from '@react-navigation/native' | ||||
| import React, { useContext, useState } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { Alert } from 'react-native' | ||||
| import ComposeContext from '../utils/createContext' | ||||
|  | ||||
| export interface Props { | ||||
|   index: number | ||||
| } | ||||
|  | ||||
| const ComposeEditAttachmentSubmit: React.FC<Props> = ({ index }) => { | ||||
|   const { composeState } = useContext(ComposeContext) | ||||
|   const navigation = useNavigation() | ||||
|   const [isSubmitting, setIsSubmitting] = useState(false) | ||||
|   const { t } = useTranslation('screenCompose') | ||||
|  | ||||
|   const theAttachment = composeState.attachments.uploads[index].remote! | ||||
|  | ||||
|   return ( | ||||
|     <HeaderRight | ||||
|       accessibilityLabel={t( | ||||
|         'content.editAttachment.header.right.accessibilityLabel' | ||||
|       )} | ||||
|       type='icon' | ||||
|       content='Save' | ||||
|       loading={isSubmitting} | ||||
|       onPress={() => { | ||||
|         setIsSubmitting(true) | ||||
|         const formData = new FormData() | ||||
|         if (theAttachment.description) { | ||||
|           formData.append('description', theAttachment.description) | ||||
|         } | ||||
|         if ( | ||||
|           theAttachment.meta?.focus?.x !== 0 || | ||||
|           theAttachment.meta.focus.y !== 0 | ||||
|         ) { | ||||
|           formData.append( | ||||
|             'focus', | ||||
|             `${theAttachment.meta?.focus?.x || 0},${ | ||||
|               -theAttachment.meta?.focus?.y || 0 | ||||
|             }` | ||||
|           ) | ||||
|         } | ||||
|  | ||||
|         theAttachment?.id && | ||||
|           apiInstance<Mastodon.Attachment>({ | ||||
|             method: 'put', | ||||
|             url: `media/${theAttachment.id}`, | ||||
|             body: formData | ||||
|           }) | ||||
|             .then(() => { | ||||
|               haptics('Success') | ||||
|               navigation.goBack() | ||||
|             }) | ||||
|             .catch(() => { | ||||
|               setIsSubmitting(false) | ||||
|               haptics('Error') | ||||
|               Alert.alert( | ||||
|                 t('content.editAttachment.header.right.failed.title'), | ||||
|                 undefined, | ||||
|                 [ | ||||
|                   { | ||||
|                     text: t( | ||||
|                       'content.editAttachment.header.right.failed.button' | ||||
|                     ), | ||||
|                     style: 'cancel' | ||||
|                   } | ||||
|                 ] | ||||
|               ) | ||||
|             }) | ||||
|       }} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default ComposeEditAttachmentSubmit | ||||
| @@ -171,21 +171,23 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => { | ||||
|                   haptics('Success') | ||||
|                 }} | ||||
|               /> | ||||
|               <Button | ||||
|                 accessibilityLabel={t('content.root.footer.attachments.edit.accessibilityLabel', { | ||||
|                   attachment: index + 1 | ||||
|                 })} | ||||
|                 type='icon' | ||||
|                 content='Edit' | ||||
|                 spacing='M' | ||||
|                 round | ||||
|                 overlay | ||||
|                 onPress={() => { | ||||
|                   navigation.navigate('Screen-Compose-EditAttachment', { | ||||
|                     index | ||||
|                   }) | ||||
|                 }} | ||||
|               /> | ||||
|               {!composeState.attachments.disallowEditing ? ( | ||||
|                 <Button | ||||
|                   accessibilityLabel={t('content.root.footer.attachments.edit.accessibilityLabel', { | ||||
|                     attachment: index + 1 | ||||
|                   })} | ||||
|                   type='icon' | ||||
|                   content='Edit' | ||||
|                   spacing='M' | ||||
|                   round | ||||
|                   overlay | ||||
|                   onPress={() => { | ||||
|                     navigation.navigate('Screen-Compose-EditAttachment', { | ||||
|                       index | ||||
|                     }) | ||||
|                   }} | ||||
|                 /> | ||||
|               ) : null} | ||||
|             </View> | ||||
|           )} | ||||
|         </View> | ||||
|   | ||||
| @@ -65,6 +65,7 @@ const composeParseState = ( | ||||
|         }), | ||||
|         ...(params.incomingStatus.media_attachments && { | ||||
|           attachments: { | ||||
|             ...(params.type === 'edit' && { disallowEditing: true }), | ||||
|             sensitive: params.incomingStatus.sensitive, | ||||
|             uploads: params.incomingStatus.media_attachments.map(media => ({ | ||||
|               remote: media | ||||
|   | ||||
							
								
								
									
										5
									
								
								src/screens/Compose/utils/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								src/screens/Compose/utils/types.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -51,6 +51,7 @@ export type ComposeState = { | ||||
|     expire: '300' | '1800' | '3600' | '21600' | '86400' | '259200' | '604800' | ||||
|   } | ||||
|   attachments: { | ||||
|     disallowEditing?: boolean // https://github.com/mastodon/mastodon/pull/20878 | ||||
|     sensitive: boolean | ||||
|     uploads: ExtendedAttachment[] | ||||
|   } | ||||
| @@ -59,8 +60,8 @@ export type ComposeState = { | ||||
|   replyToStatus?: Mastodon.Status | ||||
|   textInputFocus: { | ||||
|     current: 'text' | 'spoiler' | ||||
|     refs: { text: RefObject<TextInput>, spoiler: RefObject<TextInput> } | ||||
|     isFocused: { text: MutableRefObject<boolean>, spoiler: MutableRefObject<boolean> } | ||||
|     refs: { text: RefObject<TextInput>; spoiler: RefObject<TextInput> } | ||||
|     isFocused: { text: MutableRefObject<boolean>; spoiler: MutableRefObject<boolean> } | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user