mirror of
https://github.com/tooot-app/app
synced 2025-05-03 11:28:49 +02:00
* New translations actions.json (German) * New translations actions.json (Korean) * New translations actions.json (Chinese Simplified) * New translations actions.json (Chinese Traditional) * New translations actions.json (Vietnamese) * New translations actions.json (Italian) * New translations actions.json (Portuguese, Brazilian) * Bump packages * New translations actions.json (Chinese Simplified) * Fixed #108 * Fixed #117 * Fixed #137 * Fix badge not cleared on app launch * Update Expo workflow * Update build.yml * New context menu largely working * Fixed #158 * File format changes by `expo prebuild` * Update .gitignore * Try out notification sound * Bump packages * New Crowdin updates (#319) * New translations actions.json (Portuguese, Brazilian) * New translations timeline.json (Portuguese, Brazilian) * New translations actions.json (Portuguese, Brazilian) * New translations compose.json (Portuguese, Brazilian) * New translations tabs.json (Portuguese, Brazilian) * New translations actions.json (Vietnamese) * New translations timeline.json (German) * New translations mediaSelector.json (Italian) * New translations contextMenu.json (Vietnamese) * New translations contextMenu.json (Chinese Traditional) * New translations contextMenu.json (Chinese Simplified) * New translations contextMenu.json (Korean) * New translations contextMenu.json (Italian) * New translations contextMenu.json (German) * New translations mediaSelector.json (Portuguese, Brazilian) * New translations timeline.json (Portuguese, Brazilian) * New translations timeline.json (Italian) * New translations mediaSelector.json (German) * New translations mediaSelector.json (Vietnamese) * New translations mediaSelector.json (Chinese Traditional) * New translations mediaSelector.json (Chinese Simplified) * New translations mediaSelector.json (Korean) * New translations timeline.json (Chinese Traditional) * New translations timeline.json (Vietnamese) * New translations timeline.json (Chinese Simplified) * New translations timeline.json (Korean) * New translations contextMenu.json (Portuguese, Brazilian) * New translations mediaSelector.json (Vietnamese) * New translations contextMenu.json (Vietnamese) * New translations contextMenu.json (Vietnamese) * New translations mediaSelector.json (Chinese Simplified) * New translations contextMenu.json (German) * New translations contextMenu.json (Italian) * New translations contextMenu.json (Korean) * New translations contextMenu.json (Chinese Simplified) * New translations contextMenu.json (Portuguese, Brazilian) * Fixed #321 * Refine photo upload messaging * Prefer local language detection
153 lines
3.9 KiB
TypeScript
153 lines
3.9 KiB
TypeScript
import * as Crypto from 'expo-crypto'
|
|
import * as VideoThumbnails from 'expo-video-thumbnails'
|
|
import { Dispatch } from 'react'
|
|
import { Alert } from 'react-native'
|
|
import { ComposeAction } from '../../utils/types'
|
|
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
|
import i18next from 'i18next'
|
|
import apiInstance from '@api/instance'
|
|
import mediaSelector from '@components/mediaSelector'
|
|
import { ImageOrVideo } from 'react-native-image-crop-picker'
|
|
|
|
export interface Props {
|
|
composeDispatch: Dispatch<ComposeAction>
|
|
showActionSheetWithOptions: (
|
|
options: ActionSheetOptions,
|
|
callback: (i?: number | undefined) => void | Promise<void>
|
|
) => void
|
|
}
|
|
|
|
export const uploadAttachment = async ({
|
|
composeDispatch,
|
|
media
|
|
}: {
|
|
composeDispatch: Dispatch<ComposeAction>
|
|
media: Pick<ImageOrVideo, 'path' | 'mime' | 'width' | 'height'>
|
|
}) => {
|
|
const hash = await Crypto.digestStringAsync(
|
|
Crypto.CryptoDigestAlgorithm.SHA256,
|
|
media.path + Math.random()
|
|
)
|
|
let attachmentType: string
|
|
|
|
switch (media.mime.split('/')[0]) {
|
|
case 'image':
|
|
attachmentType = `image/${media.path.split('.')[1]}`
|
|
composeDispatch({
|
|
type: 'attachment/upload/start',
|
|
payload: {
|
|
local: { ...media, type: 'image', local_thumbnail: media.path, hash },
|
|
uploading: true
|
|
}
|
|
})
|
|
break
|
|
case 'video':
|
|
attachmentType = `video/${media.path.split('.')[1]}`
|
|
VideoThumbnails.getThumbnailAsync(media.path)
|
|
.then(({ uri, width, height }) =>
|
|
composeDispatch({
|
|
type: 'attachment/upload/start',
|
|
payload: {
|
|
local: {
|
|
...media,
|
|
type: 'video',
|
|
local_thumbnail: uri,
|
|
hash,
|
|
width,
|
|
height
|
|
},
|
|
uploading: true
|
|
}
|
|
})
|
|
)
|
|
.catch(() =>
|
|
composeDispatch({
|
|
type: 'attachment/upload/start',
|
|
payload: {
|
|
local: { ...media, type: 'video', hash },
|
|
uploading: true
|
|
}
|
|
})
|
|
)
|
|
break
|
|
default:
|
|
attachmentType = 'unknown'
|
|
composeDispatch({
|
|
type: 'attachment/upload/start',
|
|
payload: {
|
|
local: { ...media, type: 'unknown', hash },
|
|
uploading: true
|
|
}
|
|
})
|
|
break
|
|
}
|
|
|
|
const uploadFailed = (message?: string) => {
|
|
composeDispatch({
|
|
type: 'attachment/upload/fail',
|
|
payload: hash
|
|
})
|
|
Alert.alert(
|
|
i18next.t(
|
|
'screenCompose:content.root.actions.attachment.failed.alert.title'
|
|
),
|
|
message,
|
|
[
|
|
{
|
|
text: i18next.t(
|
|
'screenCompose:content.root.actions.attachment.failed.alert.button'
|
|
),
|
|
onPress: () => {}
|
|
}
|
|
]
|
|
)
|
|
}
|
|
|
|
const formData = new FormData()
|
|
formData.append('file', {
|
|
uri: `file://${media.path}`,
|
|
name: attachmentType,
|
|
type: attachmentType
|
|
} as any)
|
|
|
|
return apiInstance<Mastodon.Attachment>({
|
|
method: 'post',
|
|
version: 'v2',
|
|
url: 'media',
|
|
body: formData
|
|
})
|
|
.then(res => {
|
|
if (res.body.id) {
|
|
composeDispatch({
|
|
type: 'attachment/upload/end',
|
|
payload: { remote: res.body, local: media }
|
|
})
|
|
} else {
|
|
uploadFailed()
|
|
}
|
|
})
|
|
.catch((err: any) => {
|
|
uploadFailed(
|
|
err?.message && typeof err?.message === 'string'
|
|
? err?.message.slice(0, 50)
|
|
: undefined
|
|
)
|
|
})
|
|
}
|
|
|
|
const chooseAndUploadAttachment = async ({
|
|
composeDispatch,
|
|
showActionSheetWithOptions
|
|
}: Props): Promise<any> => {
|
|
const result = await mediaSelector({
|
|
indicateMaximum: true,
|
|
showActionSheetWithOptions
|
|
})
|
|
for (const media of result) {
|
|
uploadAttachment({ composeDispatch, media })
|
|
await new Promise(res => setTimeout(res, 500))
|
|
}
|
|
}
|
|
|
|
export default chooseAndUploadAttachment
|