Skip to content

Commit

Permalink
Merge pull request #22145 from Puneet-here/avatarViewOption
Browse files Browse the repository at this point in the history
Add avatar preview feature
  • Loading branch information
stitesExpensify authored Sep 19, 2023
2 parents ff3935f + be686ac commit fed1967
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 52 deletions.
129 changes: 78 additions & 51 deletions src/components/AvatarWithImagePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import stylePropTypes from '../styles/stylePropTypes';
import * as FileUtils from '../libs/fileDownload/FileUtils';
import getImageResolution from '../libs/fileDownload/getImageResolution';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import AttachmentModal from './AttachmentModal';
import DotIndicatorMessage from './DotIndicatorMessage';
import * as Browser from '../libs/Browser';
import withNavigationFocus, {withNavigationFocusPropTypes} from './withNavigationFocus';
Expand Down Expand Up @@ -81,6 +82,15 @@ const propTypes = {
// eslint-disable-next-line react/forbid-prop-types
errors: PropTypes.object,

/** Title for avatar preview modal */
headerTitle: PropTypes.string,

/** Avatar source for avatar preview modal */
previewSource: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

/** File name of the avatar */
originalFileName: PropTypes.string,

...withLocalizePropTypes,
...withNavigationFocusPropTypes,
};
Expand All @@ -101,6 +111,9 @@ const defaultProps = {
onErrorClose: () => {},
pendingAction: null,
errors: null,
headerTitle: '',
previewSource: '',
originalFileName: '',
};

class AvatarWithImagePicker extends React.Component {
Expand Down Expand Up @@ -279,58 +292,72 @@ class AvatarWithImagePicker extends React.Component {
</PressableWithoutFeedback>
</Tooltip>
</OfflineWithFeedback>
<AttachmentPicker type={CONST.ATTACHMENT_PICKER_TYPE.IMAGE}>
{({openPicker}) => {
const menuItems = [
{
icon: Expensicons.Upload,
text: this.props.translate('avatarWithImagePicker.uploadPhoto'),
onSelected: () => {
if (Browser.isSafari()) {
return;
}
openPicker({
onPicked: this.showAvatarCropModal,
<AttachmentModal
headerTitle={this.props.headerTitle}
source={this.props.previewSource}
originalFileName={this.props.originalFileName}
>
{({show}) => (
<AttachmentPicker type={CONST.ATTACHMENT_PICKER_TYPE.IMAGE}>
{({openPicker}) => {
const menuItems = [
{
icon: Expensicons.Upload,
text: this.props.translate('avatarWithImagePicker.uploadPhoto'),
onSelected: () => {
if (Browser.isSafari()) {
return;
}
openPicker({
onPicked: this.showAvatarCropModal,
});
},
},
];

// If current avatar isn't a default avatar, allow Remove Photo option
if (!this.props.isUsingDefaultAvatar) {
menuItems.push({
icon: Expensicons.Trashcan,
text: this.props.translate('avatarWithImagePicker.removePhoto'),
onSelected: () => {
this.setError(null, {});
this.props.onImageRemoved();
},
});
},
},
];

// If current avatar isn't a default avatar, allow Remove Photo option
if (!this.props.isUsingDefaultAvatar) {
menuItems.push({
icon: Expensicons.Trashcan,
text: this.props.translate('avatarWithImagePicker.removePhoto'),
onSelected: () => {
this.setError(null, {});
this.props.onImageRemoved();
},
});
}
return (
<PopoverMenu
isVisible={this.state.isMenuVisible}
onClose={() => this.setState({isMenuVisible: false})}
onItemSelected={(item, index) => {
this.setState({isMenuVisible: false});
// In order for the file picker to open dynamically, the click
// function must be called from within a event handler that was initiated
// by the user on Safari.
if (index === 0 && Browser.isSafari()) {
openPicker({
onPicked: this.showAvatarCropModal,
});
}
}}
menuItems={menuItems}
anchorPosition={this.props.anchorPosition}
withoutOverlay
anchorRef={this.anchorRef}
anchorAlignment={this.props.anchorAlignment}
/>
);
}}
</AttachmentPicker>

menuItems.push({
icon: Expensicons.Eye,
text: this.props.translate('avatarWithImagePicker.viewPhoto'),
onSelected: () => show(),
});
}
return (
<PopoverMenu
isVisible={this.state.isMenuVisible}
onClose={() => this.setState({isMenuVisible: false})}
onItemSelected={(item, index) => {
this.setState({isMenuVisible: false});
// In order for the file picker to open dynamically, the click
// function must be called from within a event handler that was initiated
// by the user on Safari.
if (index === 0 && Browser.isSafari()) {
openPicker({
onPicked: this.showAvatarCropModal,
});
}
}}
menuItems={menuItems}
anchorPosition={this.props.anchorPosition}
withoutOverlay
anchorRef={this.anchorRef}
anchorAlignment={this.props.anchorAlignment}
/>
);
}}
</AttachmentPicker>
)}
</AttachmentModal>
</View>
{this.state.validationError && (
<DotIndicatorMessage
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,8 @@ export default {
uploadPhoto: 'Upload photo',
removePhoto: 'Remove photo',
editImage: 'Edit photo',
viewPhoto: 'View photo',
imageUploadFailed: 'Image upload failed',
deleteWorkspaceError: 'Sorry, there was an unexpected problem deleting your workspace avatar.',
sizeExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `The selected image exceeds the maximum upload size of ${maxUploadSizeInMB}MB.`,
resolutionConstraints: ({minHeightInPx, minWidthInPx, maxHeightInPx, maxWidthInPx}: ResolutionConstraintsParams) =>
Expand All @@ -583,6 +585,7 @@ export default {
online: 'Online',
offline: 'Offline',
syncing: 'Syncing',
profileAvatar: 'Profile avatar',
},
loungeAccessPage: {
loungeAccess: 'Lounge access',
Expand Down Expand Up @@ -1299,6 +1302,7 @@ export default {
memberNotFound: 'Member not found. To invite a new member to the workspace, please use the Invite button above.',
notAuthorized: `You do not have access to this page. Are you trying to join the workspace? Please reach out to the owner of this workspace so they can add you as a member! Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`,
goToRoom: ({roomName}: GoToRoomParams) => `Go to ${roomName} room`,
workspaceAvatar: 'Workspace avatar',
},
emptyWorkspace: {
title: 'Create a new workspace',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,8 @@ export default {
uploadPhoto: 'Subir foto',
removePhoto: 'Eliminar foto',
editImage: 'Editar foto',
viewPhoto: 'Ver foto',
imageUploadFailed: 'Error al cargar la imagen',
deleteWorkspaceError: 'Lo sentimos, hubo un problema eliminando el avatar de su espacio de trabajo.',
sizeExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `La imagen supera el tamaño máximo de ${maxUploadSizeInMB}MB.`,
resolutionConstraints: ({minHeightInPx, minWidthInPx, maxHeightInPx, maxWidthInPx}: ResolutionConstraintsParams) =>
Expand All @@ -576,6 +578,7 @@ export default {
online: 'En línea',
offline: 'Desconectado',
syncing: 'Sincronizando',
profileAvatar: 'Perfil avatar',
},
loungeAccessPage: {
loungeAccess: 'Acceso a la sala vip',
Expand Down Expand Up @@ -1318,6 +1321,7 @@ export default {
memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro al espacio de trabajo, por favor, utiliza el botón Invitar que está arriba.',
notAuthorized: `No tienes acceso a esta página. ¿Estás tratando de unirte al espacio de trabajo? Comunícate con el propietario de este espacio de trabajo para que pueda añadirte como miembro. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`,
goToRoom: ({roomName}: GoToRoomParams) => `Ir a la sala ${roomName}`,
workspaceAvatar: 'Espacio de trabajo avatar',
},
emptyWorkspace: {
title: 'Crear un nuevo espacio de trabajo',
Expand Down
1 change: 1 addition & 0 deletions src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ function updateWorkspaceAvatar(policyID, file) {
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
avatar: file.uri,
originalFileName: file.name,
errorFields: {
avatar: null,
},
Expand Down
7 changes: 6 additions & 1 deletion src/pages/settings/Profile/ProfilePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ function ProfilePage(props) {
};
const currentUserDetails = props.currentUserPersonalDetails || {};
const contactMethodBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(props.loginList);
const avatarURL = lodashGet(currentUserDetails, 'avatar', '');
const accountID = lodashGet(currentUserDetails, 'accountID', '');
const emojiCode = lodashGet(props, 'currentUserPersonalDetails.status.emojiCode', '');

const profileSettingsOptions = [
Expand Down Expand Up @@ -113,7 +115,7 @@ function ProfilePage(props) {
<ScrollView>
<AvatarWithImagePicker
isUsingDefaultAvatar={UserUtils.isDefaultAvatar(lodashGet(currentUserDetails, 'avatar', ''))}
source={UserUtils.getAvatar(lodashGet(currentUserDetails, 'avatar', ''), lodashGet(currentUserDetails, 'accountID', ''))}
source={UserUtils.getAvatar(avatarURL, accountID)}
onImageSelected={PersonalDetails.updateAvatar}
onImageRemoved={PersonalDetails.deleteAvatar}
anchorPosition={styles.createMenuPositionProfile(props.windowWidth)}
Expand All @@ -123,6 +125,9 @@ function ProfilePage(props) {
errors={lodashGet(props.currentUserPersonalDetails, 'errorFields.avatar', null)}
errorRowStyles={[styles.mt6]}
onErrorClose={PersonalDetails.clearAvatarErrors}
previewSource={UserUtils.getFullSizeAvatar(avatarURL, accountID)}
originalFileName={currentUserDetails.originalFileName}
headerTitle={props.translate('profilePage.profileAvatar')}
style={[styles.mh5]}
/>
<View style={[styles.mt4]}>
Expand Down
4 changes: 4 additions & 0 deletions src/pages/workspace/WorkspaceSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {withNetwork} from '../../components/OnyxProvider';
import OfflineWithFeedback from '../../components/OfflineWithFeedback';
import Form from '../../components/Form';
import * as ReportUtils from '../../libs/ReportUtils';
import * as UserUtils from '../../libs/UserUtils';
import Avatar from '../../components/Avatar';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';
Expand Down Expand Up @@ -125,6 +126,9 @@ function WorkspaceSettingsPage(props) {
pendingAction={lodashGet(props.policy, 'pendingFields.avatar', null)}
errors={lodashGet(props.policy, 'errorFields.avatar', null)}
onErrorClose={() => Policy.clearAvatarErrors(props.policy.id)}
previewSource={UserUtils.getFullSizeAvatar(props.policy.avatar, '')}
headerTitle={props.translate('workspace.common.workspaceAvatar')}
originalFileName={props.policy.originalFileName}
/>
<OfflineWithFeedback pendingAction={lodashGet(props.policy, 'pendingFields.generalSettings')}>
<TextInput
Expand Down

0 comments on commit fed1967

Please sign in to comment.