Page MenuHomePhabricator

D7547.id25448.diff
No OneTemporary

D7547.id25448.diff

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
@@ -223,6 +223,7 @@
dispatchActionPromise(
updateUserAvatarActionTypes,
(async () => {
+ setProcessingOrUploadInProgress(false);
try {
return await updateUserAvatarCall(imageAvatarUpdateRequest);
} catch {
@@ -230,7 +231,6 @@
}
})(),
);
- setProcessingOrUploadInProgress(false);
}, [
dispatchActionPromise,
processSelectedMedia,
@@ -245,9 +245,13 @@
);
}
+const threadAvatarLoadingStatusSelector = createLoadingStatusSelector(
+ changeThreadSettingsActionTypes,
+ `${changeThreadSettingsActionTypes.started}:avatar`,
+);
function useSelectFromGalleryAndUpdateThreadAvatar(
threadID: string,
-): () => Promise<void> {
+): [() => Promise<void>, boolean] {
const dispatchActionPromise = useDispatchActionPromise();
const changeThreadSettingsCall = useServerCall(changeThreadSettings);
@@ -255,33 +259,70 @@
const processSelectedMedia = useProcessSelectedMedia();
const uploadProcessedMedia = useUploadProcessedMedia();
+ const [processingOrUploadInProgress, setProcessingOrUploadInProgress] =
+ React.useState(false);
+
+ const updateThreadAvatarLoadingStatus: LoadingStatus = useSelector(
+ threadAvatarLoadingStatusSelector,
+ );
+
+ const inProgress = React.useMemo(
+ () =>
+ processingOrUploadInProgress ||
+ updateThreadAvatarLoadingStatus === 'loading',
+ [processingOrUploadInProgress, updateThreadAvatarLoadingStatus],
+ );
+
const selectFromGalleryAndUpdateThreadAvatar = React.useCallback(async () => {
const selection: ?MediaLibrarySelection = await selectFromGallery();
if (!selection) {
- console.log('MEDIA_SELECTION_FAILED');
+ Alert.alert(
+ 'Media selection failed',
+ 'Unable to select media from Media Library.',
+ );
return;
}
- const processedMedia = await processSelectedMedia(selection);
- if (!processedMedia.success) {
- console.log('MEDIA_PROCESSING_FAILED');
- // TODO (atul): Clean up any temporary files.
+ setProcessingOrUploadInProgress(true);
+ let processedMedia;
+ try {
+ processedMedia = await processSelectedMedia(selection);
+ } catch (e) {
+ Alert.alert(
+ 'Media processing failed',
+ 'Unable to process selected media.',
+ );
+ setProcessingOrUploadInProgress(false);
+ return;
+ }
+
+ if (!processedMedia || !processedMedia.success) {
+ Alert.alert(
+ 'Media processing failed',
+ 'Unable to process selected media.',
+ );
+ setProcessingOrUploadInProgress(false);
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.
+ Alert.alert(
+ 'Media upload failed',
+ 'Unable to upload selected media. Please try again.',
+ );
+ setProcessingOrUploadInProgress(false);
return;
}
if (!uploadedMedia) {
- console.log('MEDIA_UPLOAD_FAILED');
- // TODO (atul): Clean up any temporary files.
+ Alert.alert(
+ 'Media upload failed',
+ 'Unable to upload selected media. Please try again.',
+ );
+ setProcessingOrUploadInProgress(false);
return;
}
@@ -299,7 +340,14 @@
dispatchActionPromise(
changeThreadSettingsActionTypes,
- changeThreadSettingsCall(updateThreadRequest),
+ (async () => {
+ setProcessingOrUploadInProgress(false);
+ try {
+ return await changeThreadSettingsCall(updateThreadRequest);
+ } catch {
+ Alert.alert('Avatar update failed', 'Unable to update avatar.');
+ }
+ })(),
{ customKeyName: `${changeThreadSettingsActionTypes.started}:avatar` },
);
}, [
@@ -311,17 +359,20 @@
uploadProcessedMedia,
]);
- return selectFromGalleryAndUpdateThreadAvatar;
+ return React.useMemo(
+ () => [selectFromGalleryAndUpdateThreadAvatar, inProgress],
+ [inProgress, selectFromGalleryAndUpdateThreadAvatar],
+ );
}
-function useRemoveUserAvatar(): [() => Promise<void>, boolean] {
+function useRemoveUserAvatar(): [() => void, boolean] {
const dispatchActionPromise = useDispatchActionPromise();
const updateUserAvatarCall = useServerCall(updateUserAvatar);
const updateUserAvatarLoadingStatus: LoadingStatus = useSelector(
updateUserAvatarLoadingStatusSelector,
);
- const removeUserAvatar = React.useCallback(async () => {
+ const removeUserAvatar = React.useCallback(() => {
const removeAvatarRequest: UpdateUserAvatarRemoveRequest = {
type: 'remove',
};
@@ -344,11 +395,14 @@
);
}
-function useRemoveThreadAvatar(threadID: string): () => Promise<void> {
+function useRemoveThreadAvatar(threadID: string): [() => void, boolean] {
const dispatchActionPromise = useDispatchActionPromise();
const changeThreadSettingsCall = useServerCall(changeThreadSettings);
+ const updateThreadAvatarLoadingStatus: LoadingStatus = useSelector(
+ threadAvatarLoadingStatusSelector,
+ );
- const removeThreadAvatar = React.useCallback(async () => {
+ const removeThreadAvatar = React.useCallback(() => {
const removeAvatarRequest: UpdateUserAvatarRemoveRequest = {
type: 'remove',
};
@@ -359,13 +413,24 @@
avatar: removeAvatarRequest,
},
};
+
dispatchActionPromise(
changeThreadSettingsActionTypes,
- changeThreadSettingsCall(updateThreadRequest),
+ (async () => {
+ try {
+ return await changeThreadSettingsCall(updateThreadRequest);
+ } catch {
+ Alert.alert('Avatar update failed', 'Unable to update avatar.');
+ }
+ })(),
+ { customKeyName: `${changeThreadSettingsActionTypes.started}:avatar` },
);
}, [changeThreadSettingsCall, dispatchActionPromise, threadID]);
- return removeThreadAvatar;
+ return React.useMemo(
+ () => [removeThreadAvatar, updateThreadAvatarLoadingStatus === 'loading'],
+ [removeThreadAvatar, updateThreadAvatarLoadingStatus],
+ );
}
function useENSUserAvatar(): [() => Promise<void>, boolean] {
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
@@ -1,7 +1,7 @@
// @flow
import * as React from 'react';
-import { TouchableOpacity } from 'react-native';
+import { ActivityIndicator, TouchableOpacity, View } from 'react-native';
import type { RawThreadInfo, ThreadInfo } from 'lib/types/thread-types.js';
@@ -12,6 +12,7 @@
} from './avatar-hooks.js';
import EditAvatarBadge from './edit-avatar-badge.react.js';
import ThreadAvatar from './thread-avatar.react.js';
+import { useStyles } from '../themes/colors.js';
type Props = {
+threadInfo: RawThreadInfo | ThreadInfo,
@@ -19,12 +20,17 @@
+disabled?: boolean,
};
function EditThreadAvatar(props: Props): React.Node {
+ const styles = useStyles(unboundStyles);
const { threadInfo, onPressEmojiAvatarFlow, disabled } = props;
- const selectFromGalleryAndUpdateThreadAvatar =
+ const [selectFromGalleryAndUpdateThreadAvatar, isGalleryAvatarUpdateLoading] =
useSelectFromGalleryAndUpdateThreadAvatar(threadInfo.id);
- const removeThreadAvatar = useRemoveThreadAvatar(threadInfo.id);
+ const [removeThreadAvatar, isRemoveAvatarUpdateLoading] =
+ useRemoveThreadAvatar(threadInfo.id);
+
+ const isAvatarUpdateInProgress =
+ isGalleryAvatarUpdateLoading || isRemoveAvatarUpdateLoading;
const actionSheetConfig = React.useMemo(
() => [
@@ -41,12 +47,34 @@
const showAvatarActionSheet = useShowAvatarActionSheet(actionSheetConfig);
+ let spinner;
+ if (isAvatarUpdateInProgress) {
+ spinner = (
+ <View style={styles.spinnerContainer}>
+ <ActivityIndicator color="white" size="large" />
+ </View>
+ );
+ }
+
return (
<TouchableOpacity onPress={showAvatarActionSheet} disabled={disabled}>
<ThreadAvatar threadInfo={threadInfo} size="profile" />
+ {spinner}
{!disabled ? <EditAvatarBadge /> : null}
</TouchableOpacity>
);
}
+const unboundStyles = {
+ spinnerContainer: {
+ position: 'absolute',
+ alignItems: 'center',
+ justifyContent: 'center',
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+};
+
export default EditThreadAvatar;

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 25, 11:58 AM (18 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2579308
Default Alt Text
D7547.id25448.diff (8 KB)

Event Timeline