Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to view Profile Picture in full screen #8483

Merged

Conversation

sobitneupane
Copy link
Contributor

@sobitneupane sobitneupane commented Apr 5, 2022

Details

New Feature: Added the ability to view profile pictures in full screen/larger size by clicking/tapping on them

Fixed Issues

$ #8197

Tests

  • Send an attachment to view 'Send Attachment' AttachmentModal.
  • Open an attachment to view 'Attachment' AttachmentModal.
  • Click on avatar in a user's details page to view profile picture in AttachmentModal.
    • Go to a user's detail page.
    • Click on the avatar or profile picture in the details page.
  • Verify that no errors appear in the JS console

PR Review Checklist

Contributor (PR Author) Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • I verified there are no console errors (if there’s a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained “why” the code was doing something instead of only explaining “what” the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by tagging the marketing team on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named “index.js”. All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • Any functional components have the displayName property
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose and it is
  • If a new CSS style is added I verified that:
    • A similar style doesn’t already exist
    • The style can’t be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.

PR Reviewer Checklist

  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • [ x I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • I verified there are no console errors (if there’s a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained “why” the code was doing something instead of only explaining “what” the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by tagging the marketing team on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named “index.js”. All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • Any functional components have the displayName property
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn’t already exist
    • The style can’t be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.

QA Steps

  • Send an attachment to view 'Send Attachment' AttachmentModal.
  • Open an attachment to view 'Attachment' AttachmentModal.
  • Click on avatar in a user's details page to view profile picture in AttachmentModal.
    • Go to a user's detail page.
    • Click on the avatar or profile picture in the details page.
  • Verify that no errors appear in the JS console

Screenshots

Web

Screen Shot 2022-04-05 at 12 57 53

Mobile Web

Screen Shot 2022-04-05 at 12 59 37

Desktop

Screen Shot 2022-04-05 at 12 59 37

iOS

Screen Shot 2022-04-05 at 12 59 37

Android

Screen Shot 2022-04-05 at 12 59 37

@sobitneupane sobitneupane requested a review from a team as a code owner April 5, 2022 08:21
@melvin-bot melvin-bot bot requested review from Julesssss and parasharrajat and removed request for a team April 5, 2022 08:21
Comment on lines 57 to 62
const defaultProps = {
isUploadingAttachment: false,
isProfilePicture: false,
sourceURL: null,
onConfirm: null,
originalFileName: null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if AttachmentModal still makes sense as a name for this component...

Copy link
Contributor

@Julesssss Julesssss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main issue I see is that we're showing circular avatars as square images when viewed in full. I'm going to post in the linked issue to discuss with the design team, because I'm not exactly sure there is a good solution to this:

Screenshot 2022-04-05 at 14 00 00

@parasharrajat
Copy link
Member

I agree. At this point, upload functionality should be separated from the preview component and we should change the name of this component.

@Julesssss Julesssss dismissed their stale review April 5, 2022 16:36

Checked with Design, the square default images is okay for now

@Julesssss
Copy link
Contributor

Okay, so ignore my change request about the square default images -- we'll deal with that some other time.

Copy link
Member

@parasharrajat parasharrajat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, upload functionality should be separated from the preview component and we should change the name of this component.

@sobitneupane
Copy link
Contributor Author

@Julesssss @parasharrajat Any Suggestion for the name?

@parasharrajat Are you suggesting to make two separate components one for 'uploading attachment' and other for 'previewing attachment and profile picture'? I don't see the need.

@parasharrajat
Copy link
Member

parasharrajat commented Apr 6, 2022

Are you suggesting to make two separate components one for 'uploading attachment' and the other for 'previewing attachment and profile picture'? I don't see the need.

This is good to have for separation of concern. You can use composition.

@parasharrajat
Copy link
Member

Opened the discussion here https://expensify.slack.com/archives/C01GTK53T8Q/p1649263301271969. You can share your opinion there.

@parasharrajat
Copy link
Member

Ok, others seem to agree with my change request here. More on slack.

@sobitneupane
Copy link
Contributor Author

Hello @parasharrajat, I will be away from my laptop for next 3-4 days. So, will push the update after 3-4 days.

@sobitneupane
Copy link
Contributor Author

Thank You for Your Patience.

So, here we will be adding two new components:
/src/components/PreviewAttachmentModal.js

import _ from 'underscore';
import React from 'react';
import PropTypes from 'prop-types';
import AttachmentModal from './AttachmentModal';
import withLocalize, {withLocalizePropTypes} from './withLocalize';

const propTypes = {
    /** Determines title of the modal header depending on if we are viewing a profile picture or not */
    isProfilePicture: PropTypes.bool,

    /** DisplayName to be used as headerTitle if isProfilePicture is true. */
    displayName: PropTypes.string,

    ...withLocalizePropTypes,
};

const defaultProps = {
    isProfilePicture: false,
    displayName: null,
};

const PreviewAttachmentModal = (props) => {
    const propsToPass = _.omit(props, 'displayName', 'isProfilePicture');
    return (
        <AttachmentModal
            headerTitle={props.isProfilePicture ? props.displayName : props.translate('common.attachment')}
            shouldShowDownloadButton={!props.isProfilePicture}
            /* eslint-disable-next-line react/jsx-props-no-spreading */
            {...propsToPass}
        />
    );
};

PreviewAttachmentModal.propTypes = propTypes;
PreviewAttachmentModal.defaultProps = defaultProps;
PreviewAttachmentModal.displayName = 'PreviewAttachmentModal';
export default withLocalize(PreviewAttachmentModal);

and /src/components/UploadAttachmentModal.js

import React from 'react';
import AttachmentModal from './AttachmentModal';
import withLocalize, {withLocalizePropTypes} from './withLocalize';

const propTypes = {
    ...withLocalizePropTypes,
};

const UploadAttachmentModal = props => (
    <AttachmentModal
        headerTitle={props.translate('reportActionCompose.sendAttachment')}
        /* eslint-disable-next-line react/jsx-props-no-spreading */
        {...props}
    />
);

UploadAttachmentModal.propTypes = propTypes;
UploadAttachmentModal.displayName = 'UploadAttachmentModal';
export default withLocalize(UploadAttachmentModal);

Do we need any other changes?

@parasharrajat
Copy link
Member

parasharrajat commented Apr 22, 2022

Your suggestion is fine but does not add much value. I think it would be better to just create a simple component called AttachmentPreviewModal from the AttachmentModal without all the complex upload logic and use AttachmentPreviewModal here.

OR see the requested changes.

Copy link
Member

@parasharrajat parasharrajat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OR do these changes? Let me know if you have better ideas.

src/components/AttachmentModal.js Outdated Show resolved Hide resolved
src/components/AttachmentModal.js Outdated Show resolved Hide resolved
src/components/AttachmentModal.js Outdated Show resolved Hide resolved
@@ -100,6 +100,7 @@ function formatPersonalDetails(personalDetailsList) {
const lastName = details.lastName || '';
const payPalMeAddress = details.expensify_payPalMeAddress || '';
const phoneNumber = details.phoneNumber || '';
const avatarHighResolution = details.avatar || OptionsListUtils.getDefaultAvatar(login);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this new property? And How come the default avatar is used here? we should depend on details.avatar. If a user has a default avatar, shouldn't it be available in details.avatar

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const avatar = getAvatar(details, sanitizedLogin);

function getAvatar(personalDetail, login) {
    if (personalDetail && personalDetail.avatarThumbnail) {
        return personalDetail.avatarThumbnail;
    }

    return OptionsListUtils.getDefaultAvatar(login);
}

avatar returned from formatPersonalDetails function uses avatarThumbnail and is of lower resolution for user uploaded images. So, new property is needed.

const avatarHighResolution = details.avatar || details.avatarThumbnail;

As details.avatar is undefined incase of default avatar. But, details.avatarThumbnail has the needed url. So, we can use details.avatar || details.avatarThumbnail;

source={details.avatar}
/>
<AttachmentModal
sourceURL={details.avatarHighResolution}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we do that directly here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

details.avatar being used here is avatar returned from formatPersonalDetails function which is of lower resolution. So, new property for higher resolution is added in formatPersonalDetails function which is later used here.

@@ -109,6 +110,7 @@ function formatPersonalDetails(personalDetailsList) {
timezone,
payPalMeAddress,
phoneNumber,
avatarHighResolution,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to use the avatarThumbnail and avatar properties. which means use avatarThumbnail instead of avatar and store details.avatar in the avatar.

Now use avatarThumbnail where the avatar was used.

Copy link
Contributor Author

@sobitneupane sobitneupane Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now use avatarThumbnail where the avatar was used.

@parasharrajat Do we want to do this change here? This might require massive code refactoring.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting it to be done here because of the keys. But I don't mind another PR doing that. At last, I am fine with this as well if @Julesssss is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that's okay.

source={details.avatar}
/>
<PreviewAttachmentModal
sourceURL={details.avatarHighResolution}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this directly use sourceURL={details.avatar || details.avatarThumbnail} here

title={this.props.isUploadingAttachment
? this.props.translate('reportActionCompose.sendAttachment')
: this.props.translate('common.attachment')}
title={this.props.headerTitle}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be like this.

Suggested change
title={this.props.headerTitle}
title={this.props.headerTitle || this.props.translate('common.attachment')}

Comment on lines 25 to 30
<AttachmentModal
headerTitle={props.isProfilePicture ? props.displayName : props.translate('common.attachment')}
allowDownload={!props.isProfilePicture}
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...propsToPass}
/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets remove this component and directly use AttachmentModal and set the headerTitle & allowDownload accordingly.

...withLocalizePropTypes,
};

const UploadAttachmentModal = props => (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets remove this component and directly use AttachmentModal and set the headerTitle & allowDownload accordingly.

These components are not adding much value.

@parasharrajat
Copy link
Member

I saw that you marked Comments resolved but I don't see any updates on those.

@parasharrajat
Copy link
Member

Please improve the QA steps.

Copy link
Member

@parasharrajat parasharrajat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title and the close button are not aligned on android.
image

Copy link
Member

@parasharrajat parasharrajat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

cc: @Julesssss

🎀 👀 🎀 C+ reviewed

Copy link
Contributor

@Julesssss Julesssss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I'm glad we didn't include the download button on the profile Modal -- that would be a bit creepy 😄

@parasharrajat
Copy link
Member

Looks good. I'm glad we didn't include the download button on the profile Modal -- that would be a bit creepy smile

yeah. 😅

@Julesssss
Copy link
Contributor

So the only part of the checklist that didn't pass was the offline part. But this matches the existing behavior for Profile pics/ Attachments so it's not a problem in my opinion. Merging!

@Julesssss Julesssss merged commit 75d9014 into Expensify:main Apr 29, 2022
@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

OSBotify commented May 9, 2022

🚀 Cherry-picked to staging by @sketchydroide in version: 1.1.57-8 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@Expensify/applauseleads please QA this PR and check it off on the deploy checklist if it passes.

@OSBotify
Copy link
Contributor

🚀 Deployed to production by @chiragsalian in version: 1.1.57-17 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@eVoloshchak
Copy link
Contributor

eVoloshchak commented May 9, 2023

This has caused a regression in #17454 (comment) (#16528)
The PR added AttachmentModal with isAuthTokenRequired=true to details page. If you change the avatar, it's url is blob while it is being uploaded. So if you navigate to your profile details and open avatar full screen while it's being uploaded, the app adds auth token to blob url, which ends up being invalid, so the image is never displayed. To fix this we added a check to apply auth token only if image url isn't a blob

@parasharrajat
Copy link
Member

@eVoloshchak I can't find anything related to what you explained above in the changes. Can you point it out?

@eVoloshchak
Copy link
Contributor

@parasharrajat, sure!
AttachmentModal was added here, it has isAuthTokenRequired prop always set to true

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants