verdnatura-chat/app/views/ChangeAvatarView/index.tsx

283 lines
8.1 KiB
TypeScript

import React, { useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react';
import { ScrollView, View } from 'react-native';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import ImagePicker, { Image } from 'react-native-image-crop-picker';
import { shallowEqual } from 'react-redux';
import { compareServerVersion } from '../../lib/methods/helpers';
import KeyboardView from '../../containers/KeyboardView';
import sharedStyles from '../Styles';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
import { showConfirmationAlert, showErrorAlert } from '../../lib/methods/helpers/info';
import StatusBar from '../../containers/StatusBar';
import { useTheme } from '../../theme';
import SafeAreaView from '../../containers/SafeAreaView';
import * as List from '../../containers/List';
import styles from './styles';
import { useAppSelector } from '../../lib/hooks';
import { getUserSelector } from '../../selectors/login';
import Avatar from '../../containers/Avatar';
import AvatarUrl from './AvatarUrl';
import Button from '../../containers/Button';
import I18n from '../../i18n';
import { ChatsStackParamList } from '../../stacks/types';
import { IAvatar } from '../../definitions';
import { Services } from '../../lib/services';
import AvatarSuggestion from './AvatarSuggestion';
import log from '../../lib/methods/helpers/log';
enum AvatarStateActions {
CHANGE_AVATAR = 'CHANGE_AVATAR',
RESET_USER_AVATAR = 'RESET_USER_AVATAR',
RESET_ROOM_AVATAR = 'RESET_ROOM_AVATAR'
}
interface IReducerAction {
type: AvatarStateActions;
payload?: Partial<IState>;
}
interface IState extends IAvatar {
resetUserAvatar: string;
}
const initialState = {
data: '',
url: '',
contentType: '',
service: '',
resetUserAvatar: ''
};
function reducer(state: IState, action: IReducerAction) {
const { type, payload } = action;
if (type in AvatarStateActions) {
return {
...initialState,
...payload
};
}
return state;
}
const ChangeAvatarView = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const [saving, setSaving] = useState(false);
const { colors } = useTheme();
const { userId, username, serverVersion } = useAppSelector(
state => ({
userId: getUserSelector(state).id,
username: getUserSelector(state).username,
serverVersion: state.server.version
}),
shallowEqual
);
const isDirty = useRef<boolean>(false);
const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'ChangeAvatarView'>>();
const { context, titleHeader, room, t } = useRoute<RouteProp<ChatsStackParamList, 'ChangeAvatarView'>>().params;
useLayoutEffect(() => {
navigation.setOptions({
title: titleHeader || I18n.t('Avatar')
});
}, [titleHeader, navigation]);
useEffect(() => {
navigation.addListener('beforeRemove', e => {
if (!isDirty.current) {
return;
}
e.preventDefault();
showConfirmationAlert({
title: I18n.t('Discard_changes'),
message: I18n.t('Discard_changes_description'),
confirmationText: I18n.t('Discard'),
onPress: () => {
navigation.dispatch(e.data.action);
}
});
});
}, [navigation]);
const dispatchAvatar = (action: IReducerAction) => {
isDirty.current = true;
dispatch(action);
};
const submit = async () => {
try {
setSaving(true);
console.log('🚀 ~ file: index.tsx:117 ~ submit ~ state', state);
if (context === 'room' && room?.rid) {
// Change Rooms Avatar
await changeRoomsAvatar(room.rid);
} else if (state?.url) {
// Change User's Avatar
await changeUserAvatar(state);
} else if (state.resetUserAvatar) {
// Change User's Avatar
await resetUserAvatar();
}
isDirty.current = false;
} catch (e: any) {
log(e);
return showErrorAlert(e.message, I18n.t('Oops'));
} finally {
setSaving(false);
}
return navigation.goBack();
};
const changeRoomsAvatar = async (rid: string) => {
try {
console.log('🚀 ~ file: index.tsx:135 ~ changeRoomsAvatar ~ rid', rid, state);
await Services.saveRoomSettings(rid, { roomAvatar: state?.data });
} catch (e) {
log(e);
return handleError(e, 'changing_avatar');
}
};
const changeUserAvatar = async (avatarUpload: IAvatar) => {
try {
console.log('🚀 ~ file: index.tsx:144 ~ changeUserAvatar ~ avatarUpload', avatarUpload);
await Services.setAvatarFromService(avatarUpload);
} catch (e) {
return handleError(e, 'changing_avatar');
}
};
const resetUserAvatar = async () => {
try {
console.log('🚀 ~ file: index.tsx:154 ~ resetUserAvatar ~ userId', userId);
await Services.resetAvatar(userId);
} catch (e) {
return handleError(e, 'changing_avatar');
}
};
const handleError = (e: any, action: string) => {
if (e.data && e.data.error.includes('[error-too-many-requests]')) {
throw new Error(e.data.error);
}
if (e.error && e.error === 'error-avatar-invalid-url') {
throw new Error(I18n.t(e.error, { url: e.details.url }));
}
if (I18n.isTranslated(e.error)) {
throw new Error(I18n.t(e.error));
}
throw new Error(I18n.t('There_was_an_error_while_action', { action: I18n.t(action) }));
};
const pickImage = async () => {
const options = {
cropping: true,
compressImageQuality: 0.8,
freeStyleCropEnabled: true,
cropperAvoidEmptySpaceAroundImage: false,
cropperChooseText: I18n.t('Choose'),
cropperCancelText: I18n.t('Cancel'),
includeBase64: true
};
try {
const response: Image = await ImagePicker.openPicker(options);
dispatchAvatar({
type: AvatarStateActions.CHANGE_AVATAR,
payload: { url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' }
});
} catch (error) {
log(error);
}
};
const deletingRoomAvatar = context === 'room' && state.data === null ? {} : { rid: room?.rid };
return (
<KeyboardView
style={{ backgroundColor: colors.auxiliaryBackground }}
contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128}
>
<StatusBar />
<SafeAreaView testID='change-avatar-view'>
<ScrollView
contentContainerStyle={sharedStyles.containerScrollView}
testID='change-avatar-view-list'
{...scrollPersistTaps}
>
<View style={styles.avatarContainer} testID='change-avatar-view-avatar'>
<Avatar
text={room?.name || state.resetUserAvatar || username}
avatar={state?.url}
isStatic={state?.url}
size={120}
type={t}
{...deletingRoomAvatar}
/>
</View>
{context === 'profile' ? (
<AvatarUrl
submit={value =>
dispatchAvatar({
type: AvatarStateActions.CHANGE_AVATAR,
payload: { url: value, data: value, service: 'url' }
})
}
/>
) : null}
<List.Separator style={styles.separator} />
{context === 'profile' ? (
<AvatarSuggestion
resetAvatar={() =>
dispatchAvatar({
type: AvatarStateActions.RESET_USER_AVATAR,
payload: { resetUserAvatar: `@${username}` }
})
}
username={username}
onPress={value =>
dispatchAvatar({
type: AvatarStateActions.CHANGE_AVATAR,
payload: value
})
}
/>
) : null}
<Button
title={I18n.t('Upload_image')}
type='secondary'
disabled={saving}
backgroundColor={colors.editAndUploadButtonAvatar}
onPress={pickImage}
testID='change-avatar-view-logout-other-locations'
/>
{context === 'room' && serverVersion && compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.6.0') ? (
<Button
title={I18n.t('Delete_image')}
type='primary'
disabled={saving}
backgroundColor={colors.dangerColor}
onPress={() => dispatchAvatar({ type: AvatarStateActions.RESET_ROOM_AVATAR, payload: { data: null } })}
testID='change-avatar-view-delete-my-account'
/>
) : null}
<Button
title={I18n.t('Save')}
disabled={!isDirty.current || saving}
type='primary'
loading={saving}
onPress={submit}
testID='change-avatar-view-submit'
/>
</ScrollView>
</SafeAreaView>
</KeyboardView>
);
};
export default ChangeAvatarView;