import {useCallback, useState} from 'react' import {Pressable, StyleSheet, View} from 'react-native' import {Image} from 'expo-image' import {type ModerationUI} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import { useCameraPermission, usePhotoLibraryPermission, } from '#/lib/hooks/usePermissions' import {compressIfNeeded} from '#/lib/media/manip' import {openCamera, openCropper, openPicker} from '#/lib/media/picker' import {type PickerImage} from '#/lib/media/picker.shared' import {logger} from '#/logger' import {isAndroid, isNative} from '#/platform/detection' import { type ComposerImage, compressImage, createComposerImage, } from '#/state/gallery' import {EditImageDialog} from '#/view/com/composer/photos/EditImageDialog' import {EventStopper} from '#/view/com/util/EventStopper' import {atoms as a, tokens, useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' import { Camera_Filled_Stroke2_Corner0_Rounded as CameraFilledIcon, Camera_Stroke2_Corner0_Rounded as CameraIcon, } from '#/components/icons/Camera' import {StreamingLive_Stroke2_Corner0_Rounded as LibraryIcon} from '#/components/icons/StreamingLive' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import * as Menu from '#/components/Menu' export function UserBanner({ type, banner, moderation, onSelectNewBanner, }: { type?: 'labeler' | 'default' banner?: string | null moderation?: ModerationUI onSelectNewBanner?: (img: PickerImage | null) => void }) { const t = useTheme() const {_} = useLingui() const {requestCameraAccessIfNeeded} = useCameraPermission() const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() const sheetWrapper = useSheetWrapper() const [rawImage, setRawImage] = useState() const editImageDialogControl = useDialogControl() const onOpenCamera = useCallback(async () => { if (!(await requestCameraAccessIfNeeded())) { return } onSelectNewBanner?.( await compressIfNeeded( await openCamera({ aspect: [3, 1], }), ), ) }, [onSelectNewBanner, requestCameraAccessIfNeeded]) const onOpenLibrary = useCallback(async () => { if (!(await requestPhotoAccessIfNeeded())) { return } const items = await sheetWrapper(openPicker()) if (!items[0]) { return } try { if (isNative) { onSelectNewBanner?.( await compressIfNeeded( await openCropper({ imageUri: items[0].path, aspectRatio: 3 / 1, }), ), ) } else { setRawImage(await createComposerImage(items[0])) editImageDialogControl.open() } } catch (e: any) { if (!String(e).includes('Canceled')) { logger.error('Failed to crop banner', {error: e}) } } }, [ onSelectNewBanner, requestPhotoAccessIfNeeded, sheetWrapper, editImageDialogControl, ]) const onRemoveBanner = useCallback(() => { onSelectNewBanner?.(null) }, [onSelectNewBanner]) const onChangeEditImage = useCallback( async (image: ComposerImage) => { const compressed = await compressImage(image) onSelectNewBanner?.(compressed) }, [onSelectNewBanner], ) // setUserBanner is only passed as prop on the EditProfile component return onSelectNewBanner ? ( <> {({props}) => ( {banner ? ( ) : ( )} )} {isNative && ( Upload from Camera )} {isNative ? ( Upload from Library ) : ( Upload from Files )} {!!banner && ( <> Remove Banner )} ) : banner && !((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( ) : ( ) } const styles = StyleSheet.create({ editButtonContainer: { position: 'absolute', width: 24, height: 24, bottom: 8, right: 24, borderRadius: 12, alignItems: 'center', justifyContent: 'center', }, bannerImage: { width: '100%', height: 150, }, labelerBanner: { backgroundColor: tokens.color.temp_purple, }, })