Skip to content

Commit

Permalink
Merge pull request #4801 from mananjadhav/fix/wkspace-edit-loader
Browse files Browse the repository at this point in the history
  • Loading branch information
roryabraham authored Sep 9, 2021
2 parents 77dea94 + 65f8dfa commit bc035f5
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 115 deletions.
4 changes: 4 additions & 0 deletions src/CONST.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 83 additions & 23 deletions src/components/AvatarWithImagePicker.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import _ from 'underscore';
import React from 'react';
import {Pressable, View} from 'react-native';
import {
Pressable, View, Animated, StyleSheet,
} from 'react-native';
import PropTypes from 'prop-types';
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';
import AttachmentPicker from './AttachmentPicker';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import variables from '../styles/variables';
import CONST from '../CONST';
import SpinningIndicatorAnimation from '../styles/animation/SpinningIndicatorAnimation';

const propTypes = {
/** Avatar URL to display */
Expand Down Expand Up @@ -41,6 +45,12 @@ const propTypes = {
left: PropTypes.number,
}).isRequired,

/** Flag to see if image is being uploaded */
isUploading: PropTypes.bool,

/** Size of Indicator */
size: PropTypes.string,

...withLocalizePropTypes,
};

Expand All @@ -51,17 +61,37 @@ const defaultProps = {
style: [],
DefaultAvatar: () => {},
isUsingDefaultAvatar: false,
isUploading: false,
size: CONST.AVATAR_SIZE.DEFAULT,
};

class AvatarWithImagePicker extends React.Component {
constructor(props) {
super(props);

this.animation = new SpinningIndicatorAnimation();
this.state = {
isMenuVisible: false,
};
}

componentDidMount() {
if (this.props.isUploading) {
this.animation.start();
}
}

componentDidUpdate(prevProps) {
if (!prevProps.isUploading && this.props.isUploading) {
this.animation.start();
} else if (prevProps.isUploading && !this.props.isUploading) {
this.animation.stop();
}
}

componentWillUnmount() {
this.animation.stop();
}

/**
* Create menu items list for avatar menu
*
Expand Down Expand Up @@ -97,6 +127,17 @@ 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 === CONST.AVATAR_SIZE.LARGE ? styles.statusIndicatorLarge : styles.statusIndicator,
styles.statusIndicatorOnline,
this.animation.getSyncingStyles(),
];

const indicatorIconSize = this.props.size === CONST.AVATAR_SIZE.LARGE ? variables.iconSizeXXSmall : variables.iconSizeXXXSmall;

return (
<View style={[styles.alignItemsCenter, ...additionalStyles]}>

Expand All @@ -115,26 +156,45 @@ class AvatarWithImagePicker extends React.Component {
<AttachmentPicker>
{({openPicker}) => (
<>
<Pressable
style={[styles.smallEditIcon, styles.smallAvatarEditIcon]}
onPress={() => this.setState({isMenuVisible: true})}
>
<Icon
src={Pencil}
width={variables.iconSizeSmall}
height={variables.iconSizeSmall}
fill={themeColors.iconReversed}
/>
</Pressable>
<PopoverMenu
isVisible={this.state.isMenuVisible}
onClose={() => this.setState({isMenuVisible: false})}
onItemSelected={() => this.setState({isMenuVisible: false})}
menuItems={this.createMenuItems(openPicker)}
anchorPosition={this.props.anchorPosition}
animationIn="fadeInDown"
animationOut="fadeOutUp"
/>
{
this.props.isUploading
? (
<Animated.View style={StyleSheet.flatten(indicatorStyles)}>

<Icon
src={Sync}
fill={themeColors.textReversed}
width={indicatorIconSize}
height={indicatorIconSize}
/>
</Animated.View>
)
: (
<>
<Pressable
disabled={this.props.isUploading}
style={[styles.smallEditIcon, styles.smallAvatarEditIcon]}
onPress={() => this.setState({isMenuVisible: true})}
>
<Icon
src={Pencil}
width={variables.iconSizeSmall}
height={variables.iconSizeSmall}
fill={themeColors.iconReversed}
/>
</Pressable>
<PopoverMenu
isVisible={this.state.isMenuVisible}
onClose={() => this.setState({isMenuVisible: false})}
onItemSelected={() => this.setState({isMenuVisible: false})}
menuItems={this.createMenuItems(openPicker)}
anchorPosition={this.props.anchorPosition}
animationIn="fadeInDown"
animationOut="fadeOutUp"
/>
</>
)
}
</>
)}
</AttachmentPicker>
Expand Down
72 changes: 8 additions & 64 deletions src/components/AvatarWithIndicator.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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';
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? */
Expand All @@ -34,91 +34,35 @@ 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.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 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 = [
styles.alignItemsCenter,
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 (
Expand Down
3 changes: 2 additions & 1 deletion src/libs/actions/PersonalDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,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}, true);
setPersonalDetails({avatar: response.s3url, avatarUploading: false}, true);
}
});
}
Expand Down
16 changes: 15 additions & 1 deletion src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,24 @@ 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();
}).catch(() => {
const errorMessage = translateLocal('workspace.editor.genericFailureMessage');
Growl.error(errorMessage, 5000);
});
}

/**
* 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,
Expand All @@ -263,4 +276,5 @@ export {
create,
uploadAvatar,
update,
updateLocalPolicyValues,
};
2 changes: 2 additions & 0 deletions src/pages/settings/Profile/ProfilePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,14 @@ class ProfilePage extends Component {
/>
<ScrollView style={styles.flex1} contentContainerStyle={styles.p5}>
<AvatarWithImagePicker
isUploading={this.props.myPersonalDetails.avatarUploading}
avatarURL={this.props.myPersonalDetails.avatar}
onImageSelected={setAvatar}
onImageRemoved={() => deleteAvatar(this.props.myPersonalDetails.login)}
// eslint-disable-next-line max-len
isUsingDefaultAvatar={this.props.myPersonalDetails.avatar.includes('/images/avatars/avatar')}
anchorPosition={styles.createMenuPositionProfile}
size={CONST.AVATAR_SIZE.LARGE}
/>
<Text style={[styles.mt6, styles.mb6]}>
{this.props.translate('profilePage.tellUsAboutYourself')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
Loading

0 comments on commit bc035f5

Please sign in to comment.