diff --git a/native/avatars/avatar-hooks.js b/native/avatars/avatar-hooks.js --- a/native/avatars/avatar-hooks.js +++ b/native/avatars/avatar-hooks.js @@ -7,16 +7,24 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { uploadMultimedia } from 'lib/actions/upload-actions.js'; +import { + updateUserAvatar, + updateUserAvatarActionTypes, +} from 'lib/actions/user-actions.js'; import { extensionFromFilename, filenameFromPathOrURI, } from 'lib/media/file-utils.js'; +import type { ImageAvatarDBContent } from 'lib/types/avatar-types.js'; import type { MediaLibrarySelection, MediaMissionFailure, UploadMultimediaResult, } from 'lib/types/media-types.js'; -import { useServerCall } from 'lib/utils/action-utils.js'; +import { + useDispatchActionPromise, + useServerCall, +} from 'lib/utils/action-utils.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import { getCompatibleMediaURI } from '../media/identifier-utils.js'; @@ -67,11 +75,8 @@ return processSelectedMedia; } -function useSelectAndUploadFromGallery(): () => Promise { - const processSelectedMedia = useProcessSelectedMedia(); - const uploadProcessedMedia = useUploadProcessedMedia(); - - const selectAndUploadFromGallery = React.useCallback(async () => { +function useSelectFromGallery(): () => Promise { + const selectFromGallery = React.useCallback(async () => { try { const { assets, canceled } = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, @@ -105,18 +110,72 @@ retries: 0, }; - const processedMedia = await processSelectedMedia(selection); - if (!processedMedia.success) { - return; - } - await uploadProcessedMedia(processedMedia); + return selection; } catch (e) { console.log(e); + return undefined; + } + }, []); + + return selectFromGallery; +} + +function useSelectFromGalleryAndUpdateUserAvatar(): () => Promise { + const dispatchActionPromise = useDispatchActionPromise(); + const updateUserAvatarCall = useServerCall(updateUserAvatar); + + const selectFromGallery = useSelectFromGallery(); + const processSelectedMedia = useProcessSelectedMedia(); + const uploadProcessedMedia = useUploadProcessedMedia(); + + const selectFromGalleryAndUpdateUserAvatar = React.useCallback(async () => { + const selection: ?MediaLibrarySelection = await selectFromGallery(); + if (!selection) { + console.log('MEDIA_SELECTION_FAILED'); return; } - }, [processSelectedMedia, uploadProcessedMedia]); - return selectAndUploadFromGallery; + const processedMedia = await processSelectedMedia(selection); + if (!processedMedia.success) { + console.log('MEDIA_PROCESSING_FAILED'); + // TODO (atul): Clean up any temporary files. + return; + } + + let uploadedMedia: ?UploadMultimediaResult; + try { + uploadedMedia = await uploadProcessedMedia(processedMedia); + // TODO (atul): Clean up any temporary files. + } catch { + console.log('MEDIA_UPLOAD_FAILED'); + // TODO (atul): Clean up any temporary files. + return; + } + + if (!uploadedMedia) { + console.log('MEDIA_UPLOAD_FAILED'); + // TODO (atul): Clean up any temporary files. + return; + } + + const imageAvatarUpdateRequest: ImageAvatarDBContent = { + type: 'image', + uploadID: uploadedMedia.id, + }; + + dispatchActionPromise( + updateUserAvatarActionTypes, + updateUserAvatarCall(imageAvatarUpdateRequest), + ); + }, [ + dispatchActionPromise, + processSelectedMedia, + selectFromGallery, + updateUserAvatarCall, + uploadProcessedMedia, + ]); + + return selectFromGalleryAndUpdateUserAvatar; } type ShowAvatarActionSheetOptions = { @@ -215,6 +274,6 @@ export { useUploadProcessedMedia, useProcessSelectedMedia, - useSelectAndUploadFromGallery, useShowAvatarActionSheet, + useSelectFromGalleryAndUpdateUserAvatar, }; diff --git a/native/avatars/edit-thread-avatar.react.js b/native/avatars/edit-thread-avatar.react.js --- a/native/avatars/edit-thread-avatar.react.js +++ b/native/avatars/edit-thread-avatar.react.js @@ -6,7 +6,7 @@ import type { RawThreadInfo, ThreadInfo } from 'lib/types/thread-types.js'; import { - useSelectAndUploadFromGallery, + useSelectFromGalleryAndUpdateUserAvatar, useShowAvatarActionSheet, } from './avatar-hooks.js'; import EditAvatarBadge from './edit-avatar-badge.react.js'; @@ -20,14 +20,15 @@ function EditThreadAvatar(props: Props): React.Node { const { threadInfo, onPressEmojiAvatarFlow, disabled } = props; - const selectAndUploadFromGallery = useSelectAndUploadFromGallery(); + const selectFromGalleryAndUpdateUserAvatar = + useSelectFromGalleryAndUpdateUserAvatar(); const actionSheetConfig = React.useMemo( () => [ { id: 'emoji', onPress: onPressEmojiAvatarFlow }, - { id: 'image', onPress: selectAndUploadFromGallery }, + { id: 'image', onPress: selectFromGalleryAndUpdateUserAvatar }, ], - [onPressEmojiAvatarFlow, selectAndUploadFromGallery], + [onPressEmojiAvatarFlow, selectFromGalleryAndUpdateUserAvatar], ); const showAvatarActionSheet = useShowAvatarActionSheet(actionSheetConfig); diff --git a/native/avatars/edit-user-avatar.react.js b/native/avatars/edit-user-avatar.react.js --- a/native/avatars/edit-user-avatar.react.js +++ b/native/avatars/edit-user-avatar.react.js @@ -4,7 +4,7 @@ import { TouchableOpacity } from 'react-native'; import { - useSelectAndUploadFromGallery, + useSelectFromGalleryAndUpdateUserAvatar, useShowAvatarActionSheet, } from './avatar-hooks.js'; import EditAvatarBadge from './edit-avatar-badge.react.js'; @@ -18,14 +18,15 @@ function EditUserAvatar(props: Props): React.Node { const { userID, onPressEmojiAvatarFlow, disabled } = props; - const selectAndUploadFromGallery = useSelectAndUploadFromGallery(); + const selectFromGalleryAndUpdateUserAvatar = + useSelectFromGalleryAndUpdateUserAvatar(); const actionSheetConfig = React.useMemo( () => [ { id: 'emoji', onPress: onPressEmojiAvatarFlow }, - { id: 'image', onPress: selectAndUploadFromGallery }, + { id: 'image', onPress: selectFromGalleryAndUpdateUserAvatar }, ], - [onPressEmojiAvatarFlow, selectAndUploadFromGallery], + [onPressEmojiAvatarFlow, selectFromGalleryAndUpdateUserAvatar], ); const showAvatarActionSheet = useShowAvatarActionSheet(actionSheetConfig);