Ensure once one type of media is selected, you can only select more of that type

(cherry picked from commit 1a9e6e0cdb5234667f08e3dd9107ae598941fc23)
This commit is contained in:
Eric Bailey
2025-08-13 16:14:07 -05:00
committed by Samuel Newman
parent 9b8fd2eb48
commit d498eeee46
2 changed files with 53 additions and 20 deletions
+18
View File
@@ -133,6 +133,7 @@ import * as toast from '#/components/Toast'
import {Text as NewText} from '#/components/Typography'
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
import {
type AssetType,
SelectMediaButton,
type SelectMediaButtonProps,
} from './SelectMediaButton'
@@ -1267,6 +1268,13 @@ function ComposerFooter({
const t = useTheme()
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
/*
* Once we've allowed a certain type of asset to be selected, we don't allow
* other types of media to be selected.
*/
const [selectedAssetsType, setSelectedAssetsType] = useState<
AssetType | undefined
>(undefined)
const media = post.embed.media
const images = media?.type === 'images' ? media.images : []
@@ -1304,8 +1312,17 @@ function ComposerFooter({
[dispatch],
)
/*
* Reset if the user clears any selected media
*/
if (selectedAssetsType !== undefined && !media) {
setSelectedAssetsType(undefined)
}
const onSelectAssets = useCallback<SelectMediaButtonProps['onSelectAssets']>(
async ({type, assets, errors}) => {
setSelectedAssetsType(type)
if (assets.length) {
if (type === 'image') {
const images: ComposerImage[] = []
@@ -1363,6 +1380,7 @@ function ComposerFooter({
<ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
<SelectMediaButton
disabled={isMediaSelectionDisabled}
allowedAssetTypes={selectedAssetsType}
selectedAssetsCount={selectedAssetsCount}
onSelectAssets={onSelectAssets}
/>
+35 -20
View File
@@ -26,6 +26,10 @@ import * as toast from '#/components/Toast'
export type SelectMediaButtonProps = {
disabled?: boolean
/**
* If set, this limits the types of assets that can be selected.
*/
allowedAssetTypes: AssetType | undefined
selectedAssetsCount: number
onSelectAssets: (props: {
type: AssetType
@@ -37,7 +41,7 @@ export type SelectMediaButtonProps = {
/**
* Generic asset classes, or buckets, that we support.
*/
type AssetType = 'video' | 'image' | 'gif'
export type AssetType = 'video' | 'image' | 'gif'
/**
* Shadows `ImagePickerAsset` from `expo-image-picker`, but with a guaranteed `mimeType`
@@ -231,9 +235,11 @@ async function getAdditionalVideoMetadata(asset: ValidatedImagePickerAsset) {
async function processImagePickerAssets(
assets: ImagePickerAsset[],
{
selectionLimit,
selectionCountRemaining,
allowedAssetTypes,
}: {
selectionLimit: number
selectionCountRemaining: number
allowedAssetTypes: AssetType | undefined
},
) {
/*
@@ -242,10 +248,11 @@ async function processImagePickerAssets(
const errors = new Set<SelectedAssetError>()
/*
* We only support selecting a single type of media at a time, so this
* gets set to whatever the first asset type is.
* We only support selecting a single type of media at a time, so this gets
* set to whatever the first valid asset type is, OR to whatever
* `allowedAssetTypes` is set to.
*/
let primaryMediaType: AssetType | undefined
let selectableAssetType: AssetType | undefined
/*
* This will hold the assets that we can actually use, after filtering
@@ -260,11 +267,15 @@ async function processImagePickerAssets(
continue
}
// set the primary media type to the first valid asset type
primaryMediaType = primaryMediaType || type
/*
* If we have an `allowedAssetTypes` prop, constrain to that. Otherwise,
* set this to the first valid asset type we see, and then use that to
* constrain all remaining selected assets.
*/
selectableAssetType = allowedAssetTypes || selectableAssetType || type
// ignore mixed types
if (type !== primaryMediaType) {
if (type !== selectableAssetType) {
errors.add(SelectedAssetError.MixedTypes)
continue
}
@@ -303,12 +314,12 @@ async function processImagePickerAssets(
})
}
if (primaryMediaType === 'image') {
if (supportedAssets.length > selectionLimit) {
if (selectableAssetType === 'image') {
if (supportedAssets.length > selectionCountRemaining) {
errors.add(SelectedAssetError.MaxImages)
supportedAssets = supportedAssets.slice(0, selectionLimit)
supportedAssets = supportedAssets.slice(0, selectionCountRemaining)
}
} else if (primaryMediaType === 'video') {
} else if (selectableAssetType === 'video') {
if (supportedAssets.length > 1) {
errors.add(SelectedAssetError.MaxVideos)
supportedAssets = supportedAssets.slice(0, 1)
@@ -346,7 +357,7 @@ async function processImagePickerAssets(
errors.add(SelectedAssetError.VideoTooLong)
supportedAssets = []
}
} else if (primaryMediaType === 'gif') {
} else if (selectableAssetType === 'gif') {
if (supportedAssets.length > 1) {
errors.add(SelectedAssetError.MaxGIFs)
supportedAssets = supportedAssets.slice(0, 1)
@@ -354,7 +365,7 @@ async function processImagePickerAssets(
}
return {
type: primaryMediaType!, // set above
type: selectableAssetType!, // set above
assets: supportedAssets,
errors,
}
@@ -362,6 +373,7 @@ async function processImagePickerAssets(
export function SelectMediaButton({
disabled,
allowedAssetTypes,
selectedAssetsCount,
onSelectAssets,
}: SelectMediaButtonProps) {
@@ -371,7 +383,7 @@ export function SelectMediaButton({
const sheetWrapper = useSheetWrapper()
const t = useTheme()
const selectionLimit = MAX_IMAGES - selectedAssetsCount
const selectionCountRemaining = MAX_IMAGES - selectedAssetsCount
const processSelectedAssets = useCallback(
async (rawAssets: ImagePickerAsset[]) => {
@@ -379,7 +391,10 @@ export function SelectMediaButton({
type,
assets,
errors: errorCodes,
} = await processImagePickerAssets(rawAssets, {selectionLimit})
} = await processImagePickerAssets(rawAssets, {
selectionCountRemaining,
allowedAssetTypes,
})
/*
* Convert error codes to user-friendly messages.
@@ -420,7 +435,7 @@ export function SelectMediaButton({
errors,
})
},
[_, onSelectAssets, selectionLimit],
[_, onSelectAssets, selectionCountRemaining, allowedAssetTypes],
)
const onPressSelectMedia = useCallback(async () => {
@@ -445,7 +460,7 @@ export function SelectMediaButton({
quality: 1,
allowsMultipleSelection: true,
legacy: true,
selectionLimit: isIOS ? selectionLimit : undefined,
selectionLimit: isIOS ? selectionCountRemaining : undefined,
preferredAssetRepresentationMode:
UIImagePickerPreferredAssetRepresentationMode.Current,
}),
@@ -460,7 +475,7 @@ export function SelectMediaButton({
requestVideoAccessIfNeeded,
sheetWrapper,
processSelectedAssets,
selectionLimit,
selectionCountRemaining,
])
return (