From a1d2b3871bc026ae6118e493ce1a4a9d5c84e4fb Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Tue, 24 Aug 2021 20:42:33 +0530 Subject: [PATCH 01/16] fix(wkspace-edit-loader): Added loader for AvatarWithImagePicker --- src/components/AvatarWithImagePicker.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 27f969259bd2..9f6c32a7e563 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -6,7 +6,7 @@ import Avatar from './Avatar'; import Icon from './Icon'; import PopoverMenu from './PopoverMenu'; import { - Upload, Trashcan, Pencil, + Upload, Trashcan, Pencil, Sync, } from './Icon/Expensicons'; import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; @@ -41,6 +41,9 @@ const propTypes = { left: PropTypes.number, }).isRequired, + /** Flag to see if image is being uploaded */ + isUploading: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -51,6 +54,7 @@ const defaultProps = { style: [], DefaultAvatar: () => {}, isUsingDefaultAvatar: false, + isUploading: false, }; class AvatarWithImagePicker extends React.Component { @@ -115,11 +119,12 @@ class AvatarWithImagePicker extends React.Component { {({openPicker}) => ( <> this.setState({isMenuVisible: true})} > Date: Tue, 24 Aug 2021 20:51:42 +0530 Subject: [PATCH 02/16] fix(wkspace-edit-loader): Added loader for avatar + form submission --- src/libs/actions/Policy.js | 3 ++- src/pages/workspace/WorkspaceEditorPage.js | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 661a66b6ba27..dfc85390dba7 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -239,9 +239,10 @@ function uploadAvatar(file) { * * @param {String} policyID * @param {Object} values + * @returns {Promise} */ function update(policyID, values) { - API.UpdatePolicy({policyID, value: JSON.stringify(values), lastModified: null}) + return API.UpdatePolicy({policyID, value: JSON.stringify(values), lastModified: null}) .then((policyResponse) => { if (policyResponse.jsonCode !== 200) { // Show the user feedback diff --git a/src/pages/workspace/WorkspaceEditorPage.js b/src/pages/workspace/WorkspaceEditorPage.js index 7486a7ddf299..b402e4531123 100644 --- a/src/pages/workspace/WorkspaceEditorPage.js +++ b/src/pages/workspace/WorkspaceEditorPage.js @@ -41,6 +41,8 @@ class WorkspaceEditorPage extends React.Component { name: props.policy.name, avatarURL: props.policy.avatarURL, previewAvatarURL: props.policy.avatarURL, + isAvatarUploading: false, + isSubmitting: false, }; this.submit = this.submit.bind(this); @@ -50,11 +52,11 @@ class WorkspaceEditorPage extends React.Component { } onImageSelected(image) { - this.setState({previewAvatarURL: image.uri}); + this.setState({previewAvatarURL: image.uri, isAvatarUploading: true}); // Store the upload avatar promise so we can wait for it to finish before updating the policy this.uploadAvatarPromise = uploadAvatar(image).then(url => new Promise((resolve) => { - this.setState({avatarURL: url}, resolve); + this.setState({avatarURL: url, isAvatarUploading: false}, resolve); })); } @@ -63,13 +65,20 @@ class WorkspaceEditorPage extends React.Component { } submit() { + this.setState({isSubmitting: true}); + // Wait for the upload avatar promise to finish before updating the policy this.uploadAvatarPromise.then(() => { const name = this.state.name.trim(); const avatarURL = this.state.avatarURL; const policyID = this.props.policy.id; - update(policyID, {name, avatarURL}); + update(policyID, {name, avatarURL}).then(() => { + this.setState({isSubmitting: false}); + }); + }).catch(() => { + // TODO: Throw error ? + this.setState({isSubmitting: false}); }); } @@ -85,6 +94,9 @@ class WorkspaceEditorPage extends React.Component { return null; } + const isButtonDisabled = this.state.isAvatarUploading + || (this.state.avatarURL === this.props.policy.avatarURL + && this.state.name === this.props.policy.name); return ( ( Date: Mon, 30 Aug 2021 21:45:39 +0530 Subject: [PATCH 03/16] fix(wkspace-edit-loader): Added upload indicator to AvatarWithImagePicker --- src/components/AvatarWithImagePicker.js | 152 ++++++++++++++++++++---- 1 file changed, 130 insertions(+), 22 deletions(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index a6ea72f6b17b..02dd9c777660 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -1,6 +1,8 @@ import _ from 'underscore'; import React from 'react'; -import {Pressable, View} from 'react-native'; +import { + Pressable, View, Animated, Easing, StyleSheet, +} from 'react-native'; import PropTypes from 'prop-types'; import Avatar from './Avatar'; import Icon from './Icon'; @@ -13,6 +15,7 @@ import themeColors from '../styles/themes/default'; import AttachmentPicker from './AttachmentPicker'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import variables from '../styles/variables'; +import {getSyncingStyles} from '../styles/getAvatarWithIndicatorStyles'; const propTypes = { /** Avatar URL to display */ @@ -61,11 +64,89 @@ class AvatarWithImagePicker extends React.Component { constructor(props) { super(props); + this.rotate = new Animated.Value(0); + this.scale = new Animated.Value(1); + this.startRotation = this.startRotation.bind(this); + this.startUploadIndicator = this.startUploadIndicator.bind(this); + this.stopUploadIndicator = this.stopUploadIndicator.bind(this); + this.state = { isMenuVisible: false, }; } + componentDidMount() { + if (this.props.isUploading) { + this.startUploadIndicator(); + } + } + + componentDidUpdate(prevProps) { + if (!prevProps.isUploading && this.props.isUploading) { + this.startUploadIndicator(); + } else if (prevProps.isUploading && !this.props.isUploading) { + this.stopUploadIndicator(); + } + } + + componentWillUnmount() { + this.stopUploadIndicator(); + } + + /** + * We need to manually loop the animations as `useNativeDriver` does not work well with Animated.loop. + * + * @memberof AvatarWithImagePicker + */ + startRotation() { + this.rotate.setValue(0); + Animated.timing(this.rotate, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + isInteraction: false, + useNativeDriver: true, + }).start(({finished}) => { + if (finished) { + this.startRotation(); + } + }); + } + + /** + * Start Animation for Indicator + * + * @memberof AvatarWithImagePicker + */ + startUploadIndicator() { + this.startRotation(); + Animated.spring(this.scale, { + toValue: 1.666, + tension: 1, + isInteraction: false, + useNativeDriver: true, + }).start(); + } + + /** + * Stop Animation for Indicator + * + * @memberof AvatarWithImagePicker + */ + stopUploadIndicator() { + Animated.spring(this.scale, { + toValue: 1, + tension: 1, + isInteraction: false, + useNativeDriver: true, + }).start(() => { + this.rotate.resetAnimation(); + this.scale.resetAnimation(); + this.rotate.setValue(0); + }); + } + + /** * Create menu items list for avatar menu * @@ -101,6 +182,15 @@ class AvatarWithImagePicker extends React.Component { render() { const {DefaultAvatar} = this.props; const additionalStyles = _.isArray(this.props.style) ? this.props.style : [this.props.style]; + + const indicatorStyles = [ + styles.alignItemsCenter, + styles.justifyContentCenter, + this.props.size === 'large' ? styles.statusIndicatorLarge : styles.statusIndicator, + styles.statusIndicatorOnline, + getSyncingStyles(this.rotate, this.scale), + ]; + return ( @@ -119,27 +209,45 @@ class AvatarWithImagePicker extends React.Component { {({openPicker}) => ( <> - this.setState({isMenuVisible: true})} - > - - - this.setState({isMenuVisible: false})} - onItemSelected={() => this.setState({isMenuVisible: false})} - menuItems={this.createMenuItems(openPicker)} - anchorPosition={this.props.anchorPosition} - animationIn="fadeInDown" - animationOut="fadeOutUp" - /> + { + this.props.isUploading + ? ( + + + + + ) + : ( + <> + this.setState({isMenuVisible: true})} + > + + + this.setState({isMenuVisible: false})} + onItemSelected={() => this.setState({isMenuVisible: false})} + menuItems={this.createMenuItems(openPicker)} + anchorPosition={this.props.anchorPosition} + animationIn="fadeInDown" + animationOut="fadeOutUp" + /> + + ) + } )} From 70976706f9465aa12cf82e031d34799aa649b900 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 30 Aug 2021 22:06:55 +0530 Subject: [PATCH 04/16] fix(wkspace-edit-loader): Added loading indicator to Profile Image --- src/libs/actions/PersonalDetails.js | 3 ++- src/pages/settings/Profile/ProfilePage.js | 1 + .../settings/Profile/currentUserPersonalDetailsPropsTypes.js | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js index 269e021b95a4..2ef81611772a 100644 --- a/src/libs/actions/PersonalDetails.js +++ b/src/libs/actions/PersonalDetails.js @@ -265,10 +265,11 @@ function fetchLocalCurrency() { * @param {File|Object} file */ function setAvatar(file) { + setPersonalDetails({avatarUploading: true}); API.User_UploadAvatar({file}).then((response) => { // Once we get the s3url back, update the personal details for the user with the new avatar URL if (response.jsonCode === 200) { - setPersonalDetails({avatar: response.s3url}); + setPersonalDetails({avatar: response.s3url, avatarUploading: false}); } }); } diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index c636d999a01d..7d1be6beef2e 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -206,6 +206,7 @@ class ProfilePage extends Component { /> deleteAvatar(this.props.myPersonalDetails.login)} diff --git a/src/pages/settings/Profile/currentUserPersonalDetailsPropsTypes.js b/src/pages/settings/Profile/currentUserPersonalDetailsPropsTypes.js index 45c1fc29a808..caa4d02d2e98 100644 --- a/src/pages/settings/Profile/currentUserPersonalDetailsPropsTypes.js +++ b/src/pages/settings/Profile/currentUserPersonalDetailsPropsTypes.js @@ -14,6 +14,9 @@ const currentUserPersonalDetailsPropsTypes = { /** Avatar URL of the current user from their personal details */ avatar: PropTypes.string, + /** Flag to set when Avatar uploading */ + avatarUploading: PropTypes.bool, + /** Pronouns of the current user from their personal details */ pronouns: PropTypes.string, From 0e7795744f0faea4b915e6be969b1afae794f0ed Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 30 Aug 2021 22:38:19 +0530 Subject: [PATCH 05/16] fix(wkspace-edit-loader): Changed flags from state to onyx props --- src/pages/workspace/WorkspaceEditorPage.js | 25 ++++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/pages/workspace/WorkspaceEditorPage.js b/src/pages/workspace/WorkspaceEditorPage.js index b402e4531123..8811743f8456 100644 --- a/src/pages/workspace/WorkspaceEditorPage.js +++ b/src/pages/workspace/WorkspaceEditorPage.js @@ -16,7 +16,7 @@ import Button from '../../components/Button'; import Text from '../../components/Text'; import compose from '../../libs/compose'; import { - uploadAvatar, update, + uploadAvatar, update, updateLocalPolicyValues, } from '../../libs/actions/Policy'; import Icon from '../../components/Icon'; import {Workspace} from '../../components/Icon/Expensicons'; @@ -41,8 +41,6 @@ class WorkspaceEditorPage extends React.Component { name: props.policy.name, avatarURL: props.policy.avatarURL, previewAvatarURL: props.policy.avatarURL, - isAvatarUploading: false, - isSubmitting: false, }; this.submit = this.submit.bind(this); @@ -52,11 +50,13 @@ class WorkspaceEditorPage extends React.Component { } onImageSelected(image) { - this.setState({previewAvatarURL: image.uri, isAvatarUploading: true}); + updateLocalPolicyValues(this.props.policy.id, {isAvatarUploading: true}); + this.setState({previewAvatarURL: image.uri}); // Store the upload avatar promise so we can wait for it to finish before updating the policy this.uploadAvatarPromise = uploadAvatar(image).then(url => new Promise((resolve) => { - this.setState({avatarURL: url, isAvatarUploading: false}, resolve); + updateLocalPolicyValues(this.props.policy.id, {isAvatarUploading: false}); + this.setState({avatarURL: url}, resolve); })); } @@ -65,7 +65,7 @@ class WorkspaceEditorPage extends React.Component { } submit() { - this.setState({isSubmitting: true}); + updateLocalPolicyValues(this.props.policy.id, {isPolicyUpdating: true}); // Wait for the upload avatar promise to finish before updating the policy this.uploadAvatarPromise.then(() => { @@ -73,12 +73,9 @@ class WorkspaceEditorPage extends React.Component { const avatarURL = this.state.avatarURL; const policyID = this.props.policy.id; - update(policyID, {name, avatarURL}).then(() => { - this.setState({isSubmitting: false}); - }); + update(policyID, {name, avatarURL}); }).catch(() => { - // TODO: Throw error ? - this.setState({isSubmitting: false}); + updateLocalPolicyValues(this.props.policy.id, {isPolicyUpdating: false}); }); } @@ -94,7 +91,7 @@ class WorkspaceEditorPage extends React.Component { return null; } - const isButtonDisabled = this.state.isAvatarUploading + const isButtonDisabled = policy.isAvatarUploading || (this.state.avatarURL === this.props.policy.avatarURL && this.state.name === this.props.policy.name); return ( @@ -107,7 +104,7 @@ class WorkspaceEditorPage extends React.Component { ( Date: Mon, 30 Aug 2021 22:52:02 +0530 Subject: [PATCH 06/16] fix(wskpace-edit-loader): Added function for local updates of policy values --- src/libs/actions/Policy.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index dfc85390dba7..6850ad1bbbf0 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -251,11 +251,21 @@ function update(policyID, values) { return; } - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, values); + const updatedValues = {...values, ...{isPolicyUpdating: false}}; + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, updatedValues); Navigation.dismissModal(); }); } +/** + * Sets local values for the policy + * @param {String} policyID + * @param {Object} values + */ +function updateLocalPolicyValues(policyID, values) { + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, values); +} + export { getPolicySummaries, getPolicyList, @@ -264,4 +274,5 @@ export { create, uploadAvatar, update, + updateLocalPolicyValues, }; From 55025ab5203c0daa02d6ffc9bf624b7c83db99d3 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Tue, 31 Aug 2021 00:06:18 +0530 Subject: [PATCH 07/16] fix(wkspace-edit-loader): Removed return Promise and added catch to show error --- src/libs/actions/Policy.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 6850ad1bbbf0..7763663b43b9 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -239,10 +239,9 @@ function uploadAvatar(file) { * * @param {String} policyID * @param {Object} values - * @returns {Promise} */ function update(policyID, values) { - return API.UpdatePolicy({policyID, value: JSON.stringify(values), lastModified: null}) + API.UpdatePolicy({policyID, value: JSON.stringify(values), lastModified: null}) .then((policyResponse) => { if (policyResponse.jsonCode !== 200) { // Show the user feedback @@ -254,6 +253,9 @@ function update(policyID, values) { const updatedValues = {...values, ...{isPolicyUpdating: false}}; Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, updatedValues); Navigation.dismissModal(); + }).catch(() => { + const errorMessage = translateLocal('workspace.editor.genericFailureMessage'); + Growl.error(errorMessage, 5000); }); } From fafe5ec07c47db78ca798cc09a9c0c5d0263ed7f Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Tue, 31 Aug 2021 00:06:55 +0530 Subject: [PATCH 08/16] fix(wkspace-edit-loader): Added catch to throw error --- src/pages/workspace/WorkspaceEditorPage.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceEditorPage.js b/src/pages/workspace/WorkspaceEditorPage.js index 8811743f8456..b2a4c79dd115 100644 --- a/src/pages/workspace/WorkspaceEditorPage.js +++ b/src/pages/workspace/WorkspaceEditorPage.js @@ -22,6 +22,7 @@ import Icon from '../../components/Icon'; import {Workspace} from '../../components/Icon/Expensicons'; import AvatarWithImagePicker from '../../components/AvatarWithImagePicker'; import defaultTheme from '../../styles/themes/default'; +import Growl from '../../libs/Growl'; const propTypes = { /** List of betas */ @@ -57,7 +58,10 @@ class WorkspaceEditorPage extends React.Component { this.uploadAvatarPromise = uploadAvatar(image).then(url => new Promise((resolve) => { updateLocalPolicyValues(this.props.policy.id, {isAvatarUploading: false}); this.setState({avatarURL: url}, resolve); - })); + })).catch(() => { + Growl.error(this.props.translate('workspace.editor.avatarUploadFailureMessage')); + updateLocalPolicyValues(this.props.policy.id, {isAvatarUploading: false}); + }); } onImageRemoved() { From 7713aca65fdf3bd233666f627bc191d3e7a81e5c Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Fri, 3 Sep 2021 00:59:20 +0530 Subject: [PATCH 09/16] fix(wkspace-editor-loader): Fixed icon size for loader --- src/CONST.js | 4 ++++ src/components/AvatarWithImagePicker.js | 13 ++++++++++--- src/pages/settings/Profile/ProfilePage.js | 1 + src/pages/workspace/WorkspaceEditorPage.js | 6 +++--- src/styles/variables.js | 3 ++- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index e8c1c270fc4b..80624031db5b 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -386,6 +386,10 @@ const CONST = { ICON_TYPE_ICON: 'icon', ICON_TYPE_AVATAR: 'avatar', + AVATAR_SIZE: { + LARGE: 'large', + DEFAULT: 'default', + }, REGEX: { US_PHONE: /^\+1\d{10}$/, diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 02dd9c777660..0657ead6cb59 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -16,6 +16,7 @@ import AttachmentPicker from './AttachmentPicker'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import variables from '../styles/variables'; import {getSyncingStyles} from '../styles/getAvatarWithIndicatorStyles'; +import CONST from '../CONST'; const propTypes = { /** Avatar URL to display */ @@ -47,6 +48,9 @@ const propTypes = { /** Flag to see if image is being uploaded */ isUploading: PropTypes.bool, + /** Size of Indicator */ + size: PropTypes.string, + ...withLocalizePropTypes, }; @@ -58,6 +62,7 @@ const defaultProps = { DefaultAvatar: () => {}, isUsingDefaultAvatar: false, isUploading: false, + size: CONST.AVATAR_SIZE.DEFAULT, }; class AvatarWithImagePicker extends React.Component { @@ -186,11 +191,13 @@ class AvatarWithImagePicker extends React.Component { const indicatorStyles = [ styles.alignItemsCenter, styles.justifyContentCenter, - this.props.size === 'large' ? styles.statusIndicatorLarge : styles.statusIndicator, + this.props.size === CONST.AVATAR_SIZE.LARGE ? styles.statusIndicatorLarge : styles.statusIndicator, styles.statusIndicatorOnline, getSyncingStyles(this.rotate, this.scale), ]; + const indicatorIconSize = this.props.size === CONST.AVATAR_SIZE.LARGE ? variables.iconSizeExtraSmall : variables.iconSizeXXSmall; + return ( @@ -217,8 +224,8 @@ class AvatarWithImagePicker extends React.Component { ) diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 7d1be6beef2e..c9559921c5f7 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -213,6 +213,7 @@ class ProfilePage extends Component { // eslint-disable-next-line max-len isUsingDefaultAvatar={this.props.myPersonalDetails.avatar.includes('/images/avatars/avatar')} anchorPosition={styles.createMenuPositionProfile} + size={CONST.AVATAR_SIZE.LARGE} /> {this.props.translate('profilePage.tellUsAboutYourself')} diff --git a/src/pages/workspace/WorkspaceEditorPage.js b/src/pages/workspace/WorkspaceEditorPage.js index b2a4c79dd115..d6196070ba1e 100644 --- a/src/pages/workspace/WorkspaceEditorPage.js +++ b/src/pages/workspace/WorkspaceEditorPage.js @@ -23,6 +23,7 @@ import {Workspace} from '../../components/Icon/Expensicons'; import AvatarWithImagePicker from '../../components/AvatarWithImagePicker'; import defaultTheme from '../../styles/themes/default'; import Growl from '../../libs/Growl'; +import CONST from '../../CONST'; const propTypes = { /** List of betas */ @@ -56,12 +57,10 @@ class WorkspaceEditorPage extends React.Component { // Store the upload avatar promise so we can wait for it to finish before updating the policy this.uploadAvatarPromise = uploadAvatar(image).then(url => new Promise((resolve) => { - updateLocalPolicyValues(this.props.policy.id, {isAvatarUploading: false}); this.setState({avatarURL: url}, resolve); })).catch(() => { Growl.error(this.props.translate('workspace.editor.avatarUploadFailureMessage')); - updateLocalPolicyValues(this.props.policy.id, {isAvatarUploading: false}); - }); + }).finally(() => updateLocalPolicyValues(this.props.policy.id, {isAvatarUploading: false})); } onImageRemoved() { @@ -110,6 +109,7 @@ class WorkspaceEditorPage extends React.Component { ( Date: Fri, 3 Sep 2021 01:22:44 +0530 Subject: [PATCH 10/16] fix(wkspace-edit-loader): Fixed icon sizes in indicator --- src/components/AvatarWithImagePicker.js | 2 +- src/styles/variables.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 0657ead6cb59..7ff191567415 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -196,7 +196,7 @@ class AvatarWithImagePicker extends React.Component { getSyncingStyles(this.rotate, this.scale), ]; - const indicatorIconSize = this.props.size === CONST.AVATAR_SIZE.LARGE ? variables.iconSizeExtraSmall : variables.iconSizeXXSmall; + const indicatorIconSize = this.props.size === CONST.AVATAR_SIZE.LARGE ? variables.iconSizeXXSmall : variables.iconSizeXXXSmall; return ( diff --git a/src/styles/variables.js b/src/styles/variables.js index d0fbae731411..324e4773648e 100644 --- a/src/styles/variables.js +++ b/src/styles/variables.js @@ -23,7 +23,8 @@ export default { fontSizeXXXLarge: 32, fontSizeNormalHeight: 20, lineHeightHero: 40, - iconSizeXXSmall: 6, + iconSizeXXXSmall: 4, + iconSizeXXSmall: 8, iconSizeExtraSmall: 12, iconSizeSmall: 16, iconSizeNormal: 20, From 5dbc4ebe6bc3d1688d920b7191ee5d3e4428de37 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sat, 4 Sep 2021 11:50:41 +0530 Subject: [PATCH 11/16] fix(wkspace-edit-loader): Changed recursive func call to Animate.loop --- src/components/AvatarWithImagePicker.js | 92 ++++++++++++------------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 7ff191567415..abdc750949e0 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -105,17 +105,15 @@ class AvatarWithImagePicker extends React.Component { */ startRotation() { this.rotate.setValue(0); - Animated.timing(this.rotate, { - toValue: 1, - duration: 2000, - easing: Easing.linear, - isInteraction: false, - useNativeDriver: true, - }).start(({finished}) => { - if (finished) { - this.startRotation(); - } - }); + Animated.loop( + Animated.timing(this.rotate, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + isInteraction: false, + useNativeDriver: true, + }), + ).start(); } /** @@ -217,43 +215,43 @@ class AvatarWithImagePicker extends React.Component { {({openPicker}) => ( <> { - this.props.isUploading - ? ( - + this.props.isUploading + ? ( + - - - ) - : ( - <> - this.setState({isMenuVisible: true})} - > - - - this.setState({isMenuVisible: false})} - onItemSelected={() => this.setState({isMenuVisible: false})} - menuItems={this.createMenuItems(openPicker)} - anchorPosition={this.props.anchorPosition} - animationIn="fadeInDown" - animationOut="fadeOutUp" - /> - - ) + + + ) + : ( + <> + this.setState({isMenuVisible: true})} + > + + + this.setState({isMenuVisible: false})} + onItemSelected={() => this.setState({isMenuVisible: false})} + menuItems={this.createMenuItems(openPicker)} + anchorPosition={this.props.anchorPosition} + animationIn="fadeInDown" + animationOut="fadeOutUp" + /> + + ) } )} From 69ca002ff8e2fd3715ce2e0252f4ff21fc038b0b Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 6 Sep 2021 00:52:16 +0530 Subject: [PATCH 12/16] fix(wkspace-edit-loader): Changed recursive func call to Animate.loop in AvatarIndicator --- src/components/AvatarWithIndicator.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js index 247ab5cdbec8..790418caed9e 100644 --- a/src/components/AvatarWithIndicator.js +++ b/src/components/AvatarWithIndicator.js @@ -66,17 +66,15 @@ class AvatarWithIndicator extends PureComponent { */ startRotation() { this.rotate.setValue(0); - Animated.timing(this.rotate, { - toValue: 1, - duration: 2000, - easing: Easing.linear, - isInteraction: false, - useNativeDriver: true, - }).start(({finished}) => { - if (finished) { - this.startRotation(); - } - }); + Animated.loop( + Animated.timing(this.rotate, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + isInteraction: false, + useNativeDriver: true, + }), + ).start(); } /** From 6fcdf7eff31e55aba65a5a15fde0b18f9c3ae8a5 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Tue, 7 Sep 2021 11:10:56 +0530 Subject: [PATCH 13/16] fix(wkspace-edit-loader): Moved animation to separate class with Animation.parallel --- src/components/AvatarWithImagePicker.js | 74 ++--------------- .../animation/SpinningIndicatorAnimation.js | 80 +++++++++++++++++++ 2 files changed, 88 insertions(+), 66 deletions(-) create mode 100644 src/styles/animation/SpinningIndicatorAnimation.js diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index abdc750949e0..ff8e65c5efbc 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import React from 'react'; import { - Pressable, View, Animated, Easing, StyleSheet, + Pressable, View, Animated, StyleSheet, } from 'react-native'; import PropTypes from 'prop-types'; import Avatar from './Avatar'; @@ -15,8 +15,8 @@ import themeColors from '../styles/themes/default'; import AttachmentPicker from './AttachmentPicker'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import variables from '../styles/variables'; -import {getSyncingStyles} from '../styles/getAvatarWithIndicatorStyles'; import CONST from '../CONST'; +import SpinningIndicatorAnimation from '../styles/animation/SpinningIndicatorAnimation'; const propTypes = { /** Avatar URL to display */ @@ -68,13 +68,7 @@ const defaultProps = { class AvatarWithImagePicker extends React.Component { constructor(props) { super(props); - - this.rotate = new Animated.Value(0); - this.scale = new Animated.Value(1); - this.startRotation = this.startRotation.bind(this); - this.startUploadIndicator = this.startUploadIndicator.bind(this); - this.stopUploadIndicator = this.stopUploadIndicator.bind(this); - + this.animation = new SpinningIndicatorAnimation(); this.state = { isMenuVisible: false, }; @@ -82,74 +76,22 @@ class AvatarWithImagePicker extends React.Component { componentDidMount() { if (this.props.isUploading) { - this.startUploadIndicator(); + this.animation.start(); } } componentDidUpdate(prevProps) { if (!prevProps.isUploading && this.props.isUploading) { - this.startUploadIndicator(); + this.animation.start(); } else if (prevProps.isUploading && !this.props.isUploading) { - this.stopUploadIndicator(); + this.animation.stop(); } } componentWillUnmount() { - this.stopUploadIndicator(); - } - - /** - * We need to manually loop the animations as `useNativeDriver` does not work well with Animated.loop. - * - * @memberof AvatarWithImagePicker - */ - startRotation() { - this.rotate.setValue(0); - Animated.loop( - Animated.timing(this.rotate, { - toValue: 1, - duration: 2000, - easing: Easing.linear, - isInteraction: false, - useNativeDriver: true, - }), - ).start(); - } - - /** - * Start Animation for Indicator - * - * @memberof AvatarWithImagePicker - */ - startUploadIndicator() { - this.startRotation(); - Animated.spring(this.scale, { - toValue: 1.666, - tension: 1, - isInteraction: false, - useNativeDriver: true, - }).start(); + this.animation.stop(); } - /** - * Stop Animation for Indicator - * - * @memberof AvatarWithImagePicker - */ - stopUploadIndicator() { - Animated.spring(this.scale, { - toValue: 1, - tension: 1, - isInteraction: false, - useNativeDriver: true, - }).start(() => { - this.rotate.resetAnimation(); - this.scale.resetAnimation(); - this.rotate.setValue(0); - }); - } - - /** * Create menu items list for avatar menu * @@ -191,7 +133,7 @@ class AvatarWithImagePicker extends React.Component { styles.justifyContentCenter, this.props.size === CONST.AVATAR_SIZE.LARGE ? styles.statusIndicatorLarge : styles.statusIndicator, styles.statusIndicatorOnline, - getSyncingStyles(this.rotate, this.scale), + this.animation.getSyncingStyles(), ]; const indicatorIconSize = this.props.size === CONST.AVATAR_SIZE.LARGE ? variables.iconSizeXXSmall : variables.iconSizeXXXSmall; diff --git a/src/styles/animation/SpinningIndicatorAnimation.js b/src/styles/animation/SpinningIndicatorAnimation.js new file mode 100644 index 000000000000..f8891244d27d --- /dev/null +++ b/src/styles/animation/SpinningIndicatorAnimation.js @@ -0,0 +1,80 @@ +import {Animated, Easing} from 'react-native'; + +class SpinningIndicatorAnimation { + constructor() { + this.rotate = new Animated.Value(0); + this.scale = new Animated.Value(1); + this.start = this.start.bind(this); + this.stop = this.stop.bind(this); + this.getSyncingStyles = this.getSyncingStyles.bind(this); + } + + /** + * Start Animation for Indicator + * + * @memberof AvatarWithImagePicker + * @memberof + */ + start() { + this.rotate.setValue(0); + + Animated.parallel([ + Animated.timing(this.rotate, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + isInteraction: false, + useNativeDriver: true, + }), + Animated.spring(this.scale, { + toValue: 1.666, + tension: 1, + isInteraction: false, + useNativeDriver: true, + }), + ]).start(({finished}) => { + if (finished) { + this.start(); + } + }); + } + + /** + * Stop Animation for Indicator + * + * @memberof AvatarWithImagePicker + */ + stop() { + Animated.spring(this.scale, { + toValue: 1, + tension: 1, + isInteraction: false, + useNativeDriver: true, + }).start(() => { + this.rotate.resetAnimation(); + this.scale.resetAnimation(); + this.rotate.setValue(0); + }); + } + + /** + * Get Indicator Styles while animating + * + * @returns {Object} + */ + getSyncingStyles() { + return { + transform: [{ + rotate: this.rotate.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '-360deg'], + }), + }, + { + scale: this.scale, + }], + }; + } +} + +export default SpinningIndicatorAnimation; From 7d51071b62abb6f4d91ea6e247aeb440c40d954e Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Tue, 7 Sep 2021 21:34:55 +0530 Subject: [PATCH 14/16] fix(wkspace-edit-loader): Replace animation with SpinningAnimation --- src/components/AvatarWithIndicator.js | 70 +++---------------- .../animation/SpinningIndicatorAnimation.js | 2 + src/styles/getAvatarWithIndicatorStyles.js | 23 ------ 3 files changed, 10 insertions(+), 85 deletions(-) delete mode 100644 src/styles/getAvatarWithIndicatorStyles.js diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js index 790418caed9e..bec25774a386 100644 --- a/src/components/AvatarWithIndicator.js +++ b/src/components/AvatarWithIndicator.js @@ -1,6 +1,6 @@ import React, {PureComponent} from 'react'; import { - View, StyleSheet, Animated, Easing, + View, StyleSheet, Animated, } from 'react-native'; import PropTypes from 'prop-types'; import Avatar from './Avatar'; @@ -8,7 +8,7 @@ import themeColors from '../styles/themes/default'; import styles from '../styles/styles'; import Icon from './Icon'; import {Sync} from './Icon/Expensicons'; -import {getSyncingStyles} from '../styles/getAvatarWithIndicatorStyles'; +import SpinningIndicatorAnimation from '../styles/animation/SpinningIndicatorAnimation'; const propTypes = { /** Is user active? */ @@ -34,81 +34,27 @@ class AvatarWithIndicator extends PureComponent { constructor(props) { super(props); - this.rotate = new Animated.Value(0); - this.scale = new Animated.Value(1); - this.startRotation = this.startRotation.bind(this); - this.startSyncIndicator = this.startSyncIndicator.bind(this); - this.stopSyncIndicator = this.stopSyncIndicator.bind(this); + this.animation = new SpinningIndicatorAnimation(); } componentDidMount() { if (this.props.isSyncing) { - this.startSyncIndicator(); + this.animation.start(); } } componentDidUpdate(prevProps) { if (!prevProps.isSyncing && this.props.isSyncing) { - this.startSyncIndicator(); + this.animation.start(); } else if (prevProps.isSyncing && !this.props.isSyncing) { - this.stopSyncIndicator(); + this.animation.stop(); } } componentWillUnmount() { - this.stopSyncIndicator(); + this.animation.stop(); } - /** - * We need to manually loop the animations as `useNativeDriver` does not work well with Animated.loop. - * - * @memberof AvatarWithIndicator - */ - startRotation() { - this.rotate.setValue(0); - Animated.loop( - Animated.timing(this.rotate, { - toValue: 1, - duration: 2000, - easing: Easing.linear, - isInteraction: false, - useNativeDriver: true, - }), - ).start(); - } - - /** - * Start Animation for Indicator - * - * @memberof AvatarWithIndicator - */ - startSyncIndicator() { - this.startRotation(); - Animated.spring(this.scale, { - toValue: 1.666, - tension: 1, - isInteraction: false, - useNativeDriver: true, - }).start(); - } - - /** - * Stop Animation for Indicator - * - * @memberof AvatarWithIndicator - */ - stopSyncIndicator() { - Animated.spring(this.scale, { - toValue: 1, - tension: 1, - isInteraction: false, - useNativeDriver: true, - }).start(() => { - this.rotate.resetAnimation(); - this.scale.resetAnimation(); - this.rotate.setValue(0); - }); - } render() { const indicatorStyles = [ @@ -116,7 +62,7 @@ class AvatarWithIndicator extends PureComponent { styles.justifyContentCenter, this.props.size === 'large' ? styles.statusIndicatorLarge : styles.statusIndicator, this.props.isActive ? styles.statusIndicatorOnline : styles.statusIndicatorOffline, - getSyncingStyles(this.rotate, this.scale), + this.animation.getSyncingStyles(), ]; return ( diff --git a/src/styles/animation/SpinningIndicatorAnimation.js b/src/styles/animation/SpinningIndicatorAnimation.js index f8891244d27d..964145967055 100644 --- a/src/styles/animation/SpinningIndicatorAnimation.js +++ b/src/styles/animation/SpinningIndicatorAnimation.js @@ -12,6 +12,8 @@ class SpinningIndicatorAnimation { /** * Start Animation for Indicator * + * We need to manually loop the animations as `useNativeDriver` does not work well with `Animated.loop`. + * * @memberof AvatarWithImagePicker * @memberof */ diff --git a/src/styles/getAvatarWithIndicatorStyles.js b/src/styles/getAvatarWithIndicatorStyles.js deleted file mode 100644 index 7b9a97568b15..000000000000 --- a/src/styles/getAvatarWithIndicatorStyles.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Get Indicator Styles while animating - * - * @param {Object} rotate - * @param {Object} scale - * @returns {Object} - */ -function getSyncingStyles(rotate, scale) { - return { - transform: [{ - rotate: rotate.interpolate({ - inputRange: [0, 1], - outputRange: ['0deg', '-360deg'], - }), - }, - { - scale, - }], - }; -} - -// eslint-disable-next-line import/prefer-default-export -export {getSyncingStyles}; From 9a5fad8bedc2210035fcd32c0bd2a98949a5a54c Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Wed, 8 Sep 2021 03:30:53 +0530 Subject: [PATCH 15/16] fix(wkspace-edit-loader): Rollback Animated.parallel and Animated.loop changes --- .../animation/SpinningIndicatorAnimation.js | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/styles/animation/SpinningIndicatorAnimation.js b/src/styles/animation/SpinningIndicatorAnimation.js index 964145967055..41b17bb6bd0d 100644 --- a/src/styles/animation/SpinningIndicatorAnimation.js +++ b/src/styles/animation/SpinningIndicatorAnimation.js @@ -4,43 +4,49 @@ class SpinningIndicatorAnimation { constructor() { this.rotate = new Animated.Value(0); this.scale = new Animated.Value(1); + this.startRotation = this.startRotation.bind(this); this.start = this.start.bind(this); this.stop = this.stop.bind(this); this.getSyncingStyles = this.getSyncingStyles.bind(this); } /** - * Start Animation for Indicator - * - * We need to manually loop the animations as `useNativeDriver` does not work well with `Animated.loop`. + * We need to manually loop the animations as `useNativeDriver` does not work well with Animated.loop. * - * @memberof AvatarWithImagePicker - * @memberof + * @memberof AvatarWithIndicator */ - start() { + startRotation() { this.rotate.setValue(0); - - Animated.parallel([ - Animated.timing(this.rotate, { - toValue: 1, - duration: 2000, - easing: Easing.linear, - isInteraction: false, - useNativeDriver: true, - }), - Animated.spring(this.scale, { - toValue: 1.666, - tension: 1, - isInteraction: false, - useNativeDriver: true, - }), - ]).start(({finished}) => { + Animated.timing(this.rotate, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + isInteraction: false, + useNativeDriver: true, + }).start(({finished}) => { if (finished) { - this.start(); + this.startRotation(); } }); } + + /** + * Start Animation for Indicator + * + * @memberof AvatarWithImagePicker + * @memberof + */ + start() { + this.startRotation(); + Animated.spring(this.scale, { + toValue: 1.666, + tension: 1, + isInteraction: false, + useNativeDriver: true, + }).start(); + } + /** * Stop Animation for Indicator * From 65f8dfad4396d3054f14f5c2f87a996fcac8a9e7 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Thu, 9 Sep 2021 07:18:17 +0530 Subject: [PATCH 16/16] fix(wkspace-edit-loader): Changed to Animated.loop --- .../animation/SpinningIndicatorAnimation.js | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/styles/animation/SpinningIndicatorAnimation.js b/src/styles/animation/SpinningIndicatorAnimation.js index 41b17bb6bd0d..a022376ec65d 100644 --- a/src/styles/animation/SpinningIndicatorAnimation.js +++ b/src/styles/animation/SpinningIndicatorAnimation.js @@ -11,23 +11,21 @@ class SpinningIndicatorAnimation { } /** - * We need to manually loop the animations as `useNativeDriver` does not work well with Animated.loop. + * Rotation animation for indicator in a loop * - * @memberof AvatarWithIndicator + * @memberof AvatarWithImagePicker */ startRotation() { this.rotate.setValue(0); - Animated.timing(this.rotate, { - toValue: 1, - duration: 2000, - easing: Easing.linear, - isInteraction: false, - useNativeDriver: true, - }).start(({finished}) => { - if (finished) { - this.startRotation(); - } - }); + Animated.loop( + Animated.timing(this.rotate, { + toValue: 1, + duration: 2000, + easing: Easing.linear, + isInteraction: false, + useNativeDriver: true, + }), + ).start(); } @@ -35,7 +33,6 @@ class SpinningIndicatorAnimation { * Start Animation for Indicator * * @memberof AvatarWithImagePicker - * @memberof */ start() { this.startRotation();