mirror of
https://github.com/tooot-app/app
synced 2025-05-18 20:44:18 +02:00
Fixed #548
This commit is contained in:
parent
213328ef1a
commit
36bbe5bdbd
@ -407,12 +407,12 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Screen-Compose-DraftsList'
|
name='Screen-Compose-DraftsList'
|
||||||
component={ComposeDraftsList}
|
component={ComposeDraftsList}
|
||||||
options={{ headerShown: false, presentation: 'modal' }}
|
options={{ presentation: 'modal' }}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Screen-Compose-EditAttachment'
|
name='Screen-Compose-EditAttachment'
|
||||||
component={ComposeEditAttachment}
|
component={ComposeEditAttachment}
|
||||||
options={{ headerShown: false, presentation: 'modal' }}
|
options={{ presentation: 'modal' }}
|
||||||
/>
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</ComposeContext.Provider>
|
</ComposeContext.Provider>
|
||||||
|
@ -1,49 +1,227 @@
|
|||||||
|
import apiInstance from '@api/instance'
|
||||||
import { HeaderLeft } from '@components/Header'
|
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 { 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 { 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'>> = ({
|
||||||
|
navigation,
|
||||||
const ComposeDraftsList: React.FC<
|
|
||||||
ScreenComposeStackScreenProps<'Screen-Compose-DraftsList'>
|
|
||||||
> = ({
|
|
||||||
route: {
|
route: {
|
||||||
params: { timestamp }
|
params: { timestamp }
|
||||||
},
|
}
|
||||||
navigation
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const { colors } = useTheme()
|
||||||
const { t } = useTranslation('screenCompose')
|
const { t } = useTranslation('screenCompose')
|
||||||
|
|
||||||
const children = useCallback(
|
useEffect(() => {
|
||||||
() => <ComposeDraftsListRoot timestamp={timestamp} />,
|
navigation.setOptions({
|
||||||
[]
|
title: t('content.draftsList.header.title'),
|
||||||
)
|
headerLeft: () => (
|
||||||
const headerLeft = useCallback(
|
<HeaderLeft type='icon' content='ChevronDown' onPress={() => navigation.goBack()} />
|
||||||
() => (
|
)
|
||||||
<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 (
|
return (
|
||||||
<Stack.Navigator>
|
<>
|
||||||
<Stack.Screen
|
<View
|
||||||
name='Screen-Compose-EditAttachment-Root'
|
style={{
|
||||||
children={children}
|
flexDirection: 'row',
|
||||||
options={{
|
alignItems: 'center',
|
||||||
headerLeft,
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||||
title: t('content.draftsList.header.title'),
|
padding: StyleConstants.Spacing.S,
|
||||||
headerShadowVisible: false
|
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 apiInstance from '@api/instance'
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
import haptics from '@components/haptics'
|
||||||
|
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import React from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
import ComposeEditAttachmentRoot from './EditAttachment/Root'
|
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: {
|
route: {
|
||||||
params: { index }
|
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
|
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')
|
haptics('Success')
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
{!composeState.attachments.disallowEditing ? (
|
||||||
accessibilityLabel={t('content.root.footer.attachments.edit.accessibilityLabel', {
|
<Button
|
||||||
attachment: index + 1
|
accessibilityLabel={t('content.root.footer.attachments.edit.accessibilityLabel', {
|
||||||
})}
|
attachment: index + 1
|
||||||
type='icon'
|
})}
|
||||||
content='Edit'
|
type='icon'
|
||||||
spacing='M'
|
content='Edit'
|
||||||
round
|
spacing='M'
|
||||||
overlay
|
round
|
||||||
onPress={() => {
|
overlay
|
||||||
navigation.navigate('Screen-Compose-EditAttachment', {
|
onPress={() => {
|
||||||
index
|
navigation.navigate('Screen-Compose-EditAttachment', {
|
||||||
})
|
index
|
||||||
}}
|
})
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
@ -65,6 +65,7 @@ const composeParseState = (
|
|||||||
}),
|
}),
|
||||||
...(params.incomingStatus.media_attachments && {
|
...(params.incomingStatus.media_attachments && {
|
||||||
attachments: {
|
attachments: {
|
||||||
|
...(params.type === 'edit' && { disallowEditing: true }),
|
||||||
sensitive: params.incomingStatus.sensitive,
|
sensitive: params.incomingStatus.sensitive,
|
||||||
uploads: params.incomingStatus.media_attachments.map(media => ({
|
uploads: params.incomingStatus.media_attachments.map(media => ({
|
||||||
remote: 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'
|
expire: '300' | '1800' | '3600' | '21600' | '86400' | '259200' | '604800'
|
||||||
}
|
}
|
||||||
attachments: {
|
attachments: {
|
||||||
|
disallowEditing?: boolean // https://github.com/mastodon/mastodon/pull/20878
|
||||||
sensitive: boolean
|
sensitive: boolean
|
||||||
uploads: ExtendedAttachment[]
|
uploads: ExtendedAttachment[]
|
||||||
}
|
}
|
||||||
@ -59,8 +60,8 @@ export type ComposeState = {
|
|||||||
replyToStatus?: Mastodon.Status
|
replyToStatus?: Mastodon.Status
|
||||||
textInputFocus: {
|
textInputFocus: {
|
||||||
current: 'text' | 'spoiler'
|
current: 'text' | 'spoiler'
|
||||||
refs: { text: RefObject<TextInput>, spoiler: RefObject<TextInput> }
|
refs: { text: RefObject<TextInput>; spoiler: RefObject<TextInput> }
|
||||||
isFocused: { text: MutableRefObject<boolean>, spoiler: MutableRefObject<boolean> }
|
isFocused: { text: MutableRefObject<boolean>; spoiler: MutableRefObject<boolean> }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user