(cherry picked from commit 68aea496db8df54dba5f58da267ad962c28ef995)
This commit is contained in:
Eric Bailey
2025-08-12 17:50:52 -05:00
committed by Samuel Newman
parent 84a8a31f09
commit 2016dd0993
+95 -46
View File
@@ -52,6 +52,7 @@ export enum SelectedAssetError {
MaxVideos = 'MaxVideos', MaxVideos = 'MaxVideos',
VideoTooLong = 'VideoTooLong', VideoTooLong = 'VideoTooLong',
MaxGIFs = 'MaxGIFs', MaxGIFs = 'MaxGIFs',
NoGifsOnNative = 'NoGifsOnNative',
} }
const SUPPORTED_VIDEO_MIME_TYPES = [ const SUPPORTED_VIDEO_MIME_TYPES = [
@@ -62,6 +63,12 @@ const SUPPORTED_VIDEO_MIME_TYPES = [
] as const ] as const
export type SupportedVideoMimeType = (typeof SUPPORTED_VIDEO_MIME_TYPES)[number] export type SupportedVideoMimeType = (typeof SUPPORTED_VIDEO_MIME_TYPES)[number]
function isSupportedVideoMimeType(
mimeType: string,
): mimeType is SupportedVideoMimeType {
return SUPPORTED_VIDEO_MIME_TYPES.includes(mimeType as SupportedVideoMimeType)
}
const SUPPORTED_IMAGE_MIME_TYPES = ( const SUPPORTED_IMAGE_MIME_TYPES = (
[ [
'image/gif', 'image/gif',
@@ -77,6 +84,12 @@ export type SupportedImageMimeType = Exclude<
boolean boolean
> >
function isSupportedImageMimeType(
mimeType: string,
): mimeType is SupportedImageMimeType {
return SUPPORTED_IMAGE_MIME_TYPES.includes(mimeType as SupportedImageMimeType)
}
const extensionToMimeType: Record< const extensionToMimeType: Record<
string, string,
SupportedVideoMimeType | SupportedImageMimeType SupportedVideoMimeType | SupportedImageMimeType
@@ -138,27 +151,14 @@ function getImagePickerAssetType(asset: ImagePickerAsset):
} }
/* /*
* We can now do some validation of the `mimeType` and distill it down into * Distill this down into a type "group".
* our supported `type` groups.
*/ */
let type: SelectedAsset['type'] | undefined let type: SelectedAsset['type'] | undefined
if (mimeType === 'image/gif') { if (mimeType === 'image/gif') {
type = 'gif' type = 'gif'
} else if (mimeType?.startsWith('video/')) { } else if (mimeType?.startsWith('video/')) {
/**
* We don't care about mimeType at this point on native, since the
* `processVideo` step later on will convert to `.mp4`.
*/
if (
!isWeb ||
SUPPORTED_VIDEO_MIME_TYPES.includes(mimeType as SupportedVideoMimeType)
) {
type = 'video' type = 'video'
} } else if (mimeType?.startsWith('image/')) {
} else if (
mimeType?.startsWith('image/') &&
SUPPORTED_IMAGE_MIME_TYPES.includes(mimeType as SupportedImageMimeType)
) {
type = 'image' type = 'image'
} }
@@ -203,25 +203,18 @@ async function getAdditionalVideoMetadata(asset: ValidatedImagePickerAsset) {
return await getVideoMetadata(file) return await getVideoMetadata(file)
} }
export function SelectMediaBtn({ async function processImagePickerAssets(
disabled, assets: ImagePickerAsset[],
selectedAssetsCount, {
onSelectAssets, selectionLimit,
}: Props) { }: {
const {_} = useLingui() selectionLimit: number
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() },
const {requestVideoAccessIfNeeded} = useVideoLibraryPermission() ) {
const sheetWrapper = useSheetWrapper()
const t = useTheme()
const selectionLimit = MAX_IMAGES - selectedAssetsCount
const processSelectedAssets = useCallback(
async (assets: ImagePickerAsset[]) => {
/* /*
* A deduped set of error codes, which we'll use later * A deduped set of error codes, which we'll use later
*/ */
const errorCodes = new Set<SelectedAssetError>() const errors = new Set<SelectedAssetError>()
/* /*
* We only support selecting a single type of media at a time, so this * We only support selecting a single type of media at a time, so this
@@ -238,7 +231,7 @@ export function SelectMediaBtn({
const {success, type, mimeType} = getImagePickerAssetType(asset) const {success, type, mimeType} = getImagePickerAssetType(asset)
if (!success) { if (!success) {
errorCodes.add(SelectedAssetError.Unsupported) errors.add(SelectedAssetError.Unsupported)
continue continue
} }
@@ -247,10 +240,38 @@ export function SelectMediaBtn({
// ignore mixed types // ignore mixed types
if (type !== primaryMediaType) { if (type !== primaryMediaType) {
errorCodes.add(SelectedAssetError.MixedTypes) errors.add(SelectedAssetError.MixedTypes)
continue continue
} }
if (type === 'video') {
/**
* We don't care too much about mimeType at this point on native,
* since the `processVideo` step later on will convert to `.mp4`.
*/
if (isWeb && !isSupportedVideoMimeType(mimeType)) {
errors.add(SelectedAssetError.Unsupported)
continue
}
}
if (type === 'image') {
if (!isSupportedImageMimeType(mimeType)) {
errors.add(SelectedAssetError.Unsupported)
continue
}
}
if (type === 'gif') {
if (isNative) {
errors.add(SelectedAssetError.NoGifsOnNative)
continue
}
}
/*
* All validations passed, we have an asset!
*/
supportedAssets.push({ supportedAssets.push({
mimeType, mimeType,
...asset, ...asset,
@@ -259,12 +280,12 @@ export function SelectMediaBtn({
if (primaryMediaType === 'image') { if (primaryMediaType === 'image') {
if (supportedAssets.length > selectionLimit) { if (supportedAssets.length > selectionLimit) {
errorCodes.add(SelectedAssetError.MaxImages) errors.add(SelectedAssetError.MaxImages)
supportedAssets = supportedAssets.slice(0, selectionLimit) supportedAssets = supportedAssets.slice(0, selectionLimit)
} }
} else if (primaryMediaType === 'video') { } else if (primaryMediaType === 'video') {
if (supportedAssets.length > 1) { if (supportedAssets.length > 1) {
errorCodes.add(SelectedAssetError.MaxVideos) errors.add(SelectedAssetError.MaxVideos)
supportedAssets = supportedAssets.slice(0, 1) supportedAssets = supportedAssets.slice(0, 1)
} }
@@ -277,13 +298,10 @@ export function SelectMediaBtn({
selectedVideo.width = metadata.width selectedVideo.width = metadata.width
selectedVideo.height = metadata.height selectedVideo.height = metadata.height
} catch (e: any) { } catch (e: any) {
logger.error( logger.error(`processSelectedAssets: failed to get video metadata`, {
`processSelectedAssets: failed to get video metadata`,
{
safeMessage: e.message, safeMessage: e.message,
}, })
) errors.add(SelectedAssetError.Unsupported)
errorCodes.add(SelectedAssetError.Unsupported)
supportedAssets = [] supportedAssets = []
} }
} else { } else {
@@ -300,16 +318,44 @@ export function SelectMediaBtn({
selectedVideo.duration && selectedVideo.duration &&
selectedVideo.duration > VIDEO_MAX_DURATION_MS selectedVideo.duration > VIDEO_MAX_DURATION_MS
) { ) {
errorCodes.add(SelectedAssetError.VideoTooLong) errors.add(SelectedAssetError.VideoTooLong)
supportedAssets = [] supportedAssets = []
} }
} else if (primaryMediaType === 'gif') { } else if (primaryMediaType === 'gif') {
if (supportedAssets.length > 1) { if (supportedAssets.length > 1) {
errorCodes.add(SelectedAssetError.MaxGIFs) errors.add(SelectedAssetError.MaxGIFs)
supportedAssets = supportedAssets.slice(0, 1) supportedAssets = supportedAssets.slice(0, 1)
} }
} }
return {
type: primaryMediaType!, // set above
assets: supportedAssets,
errors,
}
}
export function SelectMediaBtn({
disabled,
selectedAssetsCount,
onSelectAssets,
}: Props) {
const {_} = useLingui()
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
const {requestVideoAccessIfNeeded} = useVideoLibraryPermission()
const sheetWrapper = useSheetWrapper()
const t = useTheme()
const selectionLimit = MAX_IMAGES - selectedAssetsCount
const processSelectedAssets = useCallback(
async (rawAssets: ImagePickerAsset[]) => {
const {
type,
assets,
errors: errorCodes,
} = await processImagePickerAssets(rawAssets, {selectionLimit})
/* /*
* Convert error codes to user-friendly messages. * Convert error codes to user-friendly messages.
*/ */
@@ -333,16 +379,19 @@ export function SelectMediaBtn({
[SelectedAssetError.MaxGIFs]: _( [SelectedAssetError.MaxGIFs]: _(
msg`You can only select one GIF at a time.`, msg`You can only select one GIF at a time.`,
), ),
[SelectedAssetError.NoGifsOnNative]: _(
msg`GIFs are only supported on web at this time.`,
),
}[error] }[error]
}) })
/* /*
* Finally, report the selected assets and any errors back to the * Report the selected assets and any errors back to the
* composer. * composer.
*/ */
onSelectAssets({ onSelectAssets({
type: primaryMediaType!, type,
assets: supportedAssets, assets,
errors, errors,
}) })
}, },