diff --git a/native/avatars/edit-user-avatar-provider.react.js b/native/avatars/edit-user-avatar-provider.react.js index 93361e5cd..a53d80046 100644 --- a/native/avatars/edit-user-avatar-provider.react.js +++ b/native/avatars/edit-user-avatar-provider.react.js @@ -1,174 +1,178 @@ // @flow import * as React from 'react'; import { Alert } from 'react-native'; import { updateUserAvatar, updateUserAvatarActionTypes, } from 'lib/actions/user-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { NativeMediaSelection } from 'lib/types/media-types.js'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils.js'; import { selectFromGallery, useUploadSelectedMedia } from './avatar-hooks.js'; import { useSelector } from '../redux/redux-utils.js'; export type UserAvatarSelection = | { +needsUpload: true, +mediaSelection: NativeMediaSelection } | { +needsUpload: false, +updateUserAvatarRequest: UpdateUserAvatarRequest }; type RegistrationMode = | { +registrationMode: 'off' } | { +registrationMode: 'on', +successCallback: UserAvatarSelection => mixed, }; const registrationModeOff = { registrationMode: 'off' }; export type EditUserAvatarContextType = { +userAvatarSaveInProgress: boolean, +selectFromGalleryAndUpdateUserAvatar: () => Promise, +updateImageUserAvatar: (selection: NativeMediaSelection) => Promise, +setUserAvatar: (avatarRequest: UpdateUserAvatarRequest) => Promise, +setRegistrationMode: (registrationMode: RegistrationMode) => void, + +registrationModeEnabled: boolean, }; const EditUserAvatarContext: React.Context = React.createContext(); const updateUserAvatarLoadingStatusSelector = createLoadingStatusSelector( updateUserAvatarActionTypes, ); const displayFailureAlert = () => Alert.alert( 'Couldn’t save avatar', 'Please try again later', [{ text: 'OK' }], { cancelable: true }, ); type Props = { +children: React.Node, }; function EditUserAvatarProvider(props: Props): React.Node { const { children } = props; const [registrationMode, setRegistrationMode] = React.useState(registrationModeOff); const dispatchActionPromise = useDispatchActionPromise(); const updateUserAvatarCall = useServerCall(updateUserAvatar); const [userAvatarMediaUploadInProgress, setUserAvatarMediaUploadInProgress] = React.useState(false); const updateUserAvatarLoadingStatus: LoadingStatus = useSelector( updateUserAvatarLoadingStatusSelector, ); const userAvatarSaveInProgress = userAvatarMediaUploadInProgress || updateUserAvatarLoadingStatus === 'loading'; const uploadSelectedMedia = useUploadSelectedMedia( setUserAvatarMediaUploadInProgress, ); const updateImageUserAvatar = React.useCallback( async (selection: NativeMediaSelection) => { if (registrationMode.registrationMode === 'on') { registrationMode.successCallback({ needsUpload: true, mediaSelection: selection, }); return; } const imageAvatarUpdateRequest = await uploadSelectedMedia(selection); if (!imageAvatarUpdateRequest) { return; } const promise = (async () => { setUserAvatarMediaUploadInProgress(false); try { return await updateUserAvatarCall(imageAvatarUpdateRequest); } catch (e) { displayFailureAlert(); throw e; } })(); dispatchActionPromise(updateUserAvatarActionTypes, promise); await promise; }, [ registrationMode, uploadSelectedMedia, updateUserAvatarCall, dispatchActionPromise, ], ); const selectFromGalleryAndUpdateUserAvatar = React.useCallback(async () => { const selection = await selectFromGallery(); if (!selection) { return; } await updateImageUserAvatar(selection); }, [updateImageUserAvatar]); const setUserAvatar = React.useCallback( async (request: UpdateUserAvatarRequest) => { if (registrationMode.registrationMode === 'on') { registrationMode.successCallback({ needsUpload: false, updateUserAvatarRequest: request, }); return; } const promise = (async () => { try { return await updateUserAvatarCall(request); } catch (e) { displayFailureAlert(); throw e; } })(); dispatchActionPromise(updateUserAvatarActionTypes, promise); await promise; }, [registrationMode, updateUserAvatarCall, dispatchActionPromise], ); + const registrationModeEnabled = registrationMode.registrationMode === 'on'; const context = React.useMemo( () => ({ userAvatarSaveInProgress, selectFromGalleryAndUpdateUserAvatar, updateImageUserAvatar, setUserAvatar, setRegistrationMode, + registrationModeEnabled, }), [ userAvatarSaveInProgress, selectFromGalleryAndUpdateUserAvatar, updateImageUserAvatar, setUserAvatar, setRegistrationMode, + registrationModeEnabled, ], ); return ( {children} ); } export { EditUserAvatarContext, EditUserAvatarProvider }; diff --git a/native/avatars/edit-user-avatar.react.js b/native/avatars/edit-user-avatar.react.js index bdc61aab0..e2f535f69 100644 --- a/native/avatars/edit-user-avatar.react.js +++ b/native/avatars/edit-user-avatar.react.js @@ -1,132 +1,147 @@ // @flow import { useNavigation } from '@react-navigation/native'; import invariant from 'invariant'; import * as React from 'react'; import { ActivityIndicator, TouchableOpacity, View } from 'react-native'; import { useENSAvatar } from 'lib/hooks/ens-cache.js'; import { getETHAddressForUserInfo } from 'lib/shared/account-utils.js'; import type { GenericUserInfoWithAvatar } from 'lib/types/avatar-types.js'; import { useShowAvatarActionSheet } from './avatar-hooks.js'; import EditAvatarBadge from './edit-avatar-badge.react.js'; import { EditUserAvatarContext } from './edit-user-avatar-provider.react.js'; import UserAvatar from './user-avatar.react.js'; import { EmojiUserAvatarCreationRouteName, UserAvatarCameraModalRouteName, + EmojiAvatarSelectionRouteName, + RegistrationUserAvatarCameraModalRouteName, } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; type Props = | { +userID: ?string, +disabled?: boolean } | { +userInfo: ?GenericUserInfoWithAvatar, +disabled?: boolean }; function EditUserAvatar(props: Props): React.Node { const editUserAvatarContext = React.useContext(EditUserAvatarContext); invariant(editUserAvatarContext, 'editUserAvatarContext should be set'); const { userAvatarSaveInProgress, selectFromGalleryAndUpdateUserAvatar, setUserAvatar, + registrationModeEnabled, } = editUserAvatarContext; const currentUserInfo = useSelector(state => state.currentUserInfo); const userInfoProp = props.userInfo; const userInfo: ?GenericUserInfoWithAvatar = userInfoProp ?? currentUserInfo; const ethAddress = React.useMemo( () => getETHAddressForUserInfo(userInfo), [userInfo], ); const ensAvatarURI = useENSAvatar(ethAddress); const { navigate } = useNavigation(); - const navigateToUserEmojiAvatarCreation = React.useCallback(() => { - navigate(EmojiUserAvatarCreationRouteName); - }, [navigate]); + const usernameOrEthAddress = userInfo?.username; + const navigateToEmojiSelection = React.useCallback(() => { + if (!registrationModeEnabled) { + navigate(EmojiUserAvatarCreationRouteName); + return; + } + navigate<'EmojiAvatarSelection'>({ + name: EmojiAvatarSelectionRouteName, + params: { usernameOrEthAddress }, + }); + }, [navigate, registrationModeEnabled, usernameOrEthAddress]); const navigateToCamera = React.useCallback(() => { - navigate(UserAvatarCameraModalRouteName); - }, [navigate]); + navigate( + registrationModeEnabled + ? RegistrationUserAvatarCameraModalRouteName + : UserAvatarCameraModalRouteName, + ); + }, [navigate, registrationModeEnabled]); const setENSUserAvatar = React.useCallback(() => { setUserAvatar({ type: 'ens' }); }, [setUserAvatar]); const removeUserAvatar = React.useCallback(() => { setUserAvatar({ type: 'remove' }); }, [setUserAvatar]); const hasCurrentAvatar = !!userInfo?.avatar; const actionSheetConfig = React.useMemo(() => { const configOptions = [ - { id: 'emoji', onPress: navigateToUserEmojiAvatarCreation }, + { id: 'emoji', onPress: navigateToEmojiSelection }, { id: 'image', onPress: selectFromGalleryAndUpdateUserAvatar }, { id: 'camera', onPress: navigateToCamera }, ]; if (ensAvatarURI) { configOptions.push({ id: 'ens', onPress: setENSUserAvatar }); } if (hasCurrentAvatar) { configOptions.push({ id: 'remove', onPress: removeUserAvatar }); } return configOptions; }, [ hasCurrentAvatar, ensAvatarURI, navigateToCamera, - navigateToUserEmojiAvatarCreation, + navigateToEmojiSelection, removeUserAvatar, setENSUserAvatar, selectFromGalleryAndUpdateUserAvatar, ]); const showAvatarActionSheet = useShowAvatarActionSheet(actionSheetConfig); const styles = useStyles(unboundStyles); let spinner; if (userAvatarSaveInProgress) { spinner = ( ); } const { userID } = props; const userAvatar = userID ? ( ) : ( ); const { disabled } = props; return ( {userAvatar} {spinner} {!disabled ? : null} ); } const unboundStyles = { spinnerContainer: { position: 'absolute', alignItems: 'center', justifyContent: 'center', top: 0, bottom: 0, left: 0, right: 0, }, }; export default EditUserAvatar;