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

Added tapable links to user's login information on DetailsPage #3870

Merged
merged 11 commits into from
Jul 12, 2021
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Pressable} from 'react-native';
import MenuItem from '../../../components/MenuItem';
import Tooltip from '../../../components/Tooltip';
import Icon from '../../../components/Icon';
import styles, {getIconFillColor, getButtonBackgroundColorStyle} from '../../../styles/styles';
import getButtonState from '../../../libs/getButtonState';
import MenuItem from './MenuItem';
import Tooltip from './Tooltip';
import Icon from './Icon';
import styles, {getIconFillColor, getButtonBackgroundColorStyle} from '../styles/styles';
import getButtonState from '../libs/getButtonState';

const propTypes = {
/** Icon Component */
Expand All @@ -25,15 +25,19 @@ const propTypes = {

/** Callback to fire when the item is pressed */
onPress: PropTypes.func.isRequired,

/** Automatically reset the success status */
autoReset: PropTypes.bool,
};

const defaultProps = {
isMini: false,
successIcon: null,
successText: '',
autoReset: false,
};

class ReportActionContextMenuItem extends Component {
class ContextMenuItem extends Component {
constructor(props) {
super(props);
this.state = {
Expand All @@ -42,6 +46,12 @@ class ReportActionContextMenuItem extends Component {
this.triggerPressAndUpdateSuccess = this.triggerPressAndUpdateSuccess.bind(this);
}

componentWillUnmount() {
if (this.successResetTimer) {
clearTimeout(this.successResetTimer);
}
}

/**
* Called on button press and mark the run
*/
Expand All @@ -57,6 +67,9 @@ class ReportActionContextMenuItem extends Component {
this.setState({
success: true,
});
if (this.props.autoReset) {
this.successResetTimer = setTimeout(() => this.setState({success: false}), 1800);
}
}
}

Expand Down Expand Up @@ -99,8 +112,8 @@ class ReportActionContextMenuItem extends Component {
}
}

ReportActionContextMenuItem.propTypes = propTypes;
ReportActionContextMenuItem.defaultProps = defaultProps;
ReportActionContextMenuItem.displayName = 'ReportActionContextMenuItem';
ContextMenuItem.propTypes = propTypes;
ContextMenuItem.defaultProps = defaultProps;
ContextMenuItem.displayName = 'ContextMenuItem';

export default ReportActionContextMenuItem;
export default ContextMenuItem;
79 changes: 79 additions & 0 deletions src/components/TappableCopy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import {View, Pressable, Linking} from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles/styles';
import compose from '../libs/compose';
import {Checkmark, Clipboard as ClipboardIcon} from './Icon/Expensicons';
import Clipboard from '../libs/Clipboard';
import ContextMenuItem from './ContextMenuItem';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
import CONST from '../CONST';

const propTypes = {
/** Children to wrap in TappableCopy. */
children: PropTypes.node.isRequired,

/** Styles to be assigned to Container */
style: PropTypes.arrayOf(PropTypes.object),
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved

/** Decides Tap behaviour. */
type: PropTypes.oneOf([CONST.LOGIN_TYPE.PHONE, CONST.LOGIN_TYPE.EMAIL]),

/** Value to be copied or passed via tap. */
value: PropTypes.string.isRequired,

...windowDimensionsPropTypes,
...withLocalizePropTypes,
};

const defaultProps = {
style: [],
type: undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't quite understand this. When are we using this component and why would it not have a type? It looks like if there's no type we just return an empty View. 🤔

Can we maybe give this a better name than TappableCopy? It looks like it has a pretty specific purpose i.e. wrap anything (I don't see anything that limits this to "copy") in a Pressable and then do an email or phone link or generically copy the text to clipboard.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe something like CommunicationsLink

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure.

Copy link
Contributor

Choose a reason for hiding this comment

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

There were two points to address so I'm reopening. Please allow reviewers to resolve comments.

};

const TappableCopy = props => (
<View style={[styles.flexRow, styles.pRelative, ...props.style]}>
{props.type && props.isSmallScreenWidth
chiragsalian marked this conversation as resolved.
Show resolved Hide resolved
? (
<Pressable
onPress={() => Linking.openURL(
props.type === CONST.LOGIN_TYPE.PHONE
? `tel:${props.value}`
: `mailto:${props.value}`,
)}
>
{props.children}
</Pressable>
)
: props.children}
{props.type && !props.isSmallScreenWidth
&& (
<View style={[
styles.pAbsolute,
styles.alignItemsCenter,
styles.justifyContentCenter,
{right: -36, top: 0, bottom: 0}]}
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved
>
<ContextMenuItem
icon={ClipboardIcon}
text={props.translate('reportActionContextMenu.copyToClipboard')}
successIcon={Checkmark}
successText={props.translate('reportActionContextMenu.copied')}
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved
isMini
autoReset
onPress={() => Clipboard.setString(props.value)}
/>
</View>
)}
</View>
);

TappableCopy.propTypes = propTypes;
TappableCopy.defaultProps = defaultProps;
TappableCopy.displayName = 'TappableCopy';

export default compose(
withWindowDimensions,
withLocalize,
)(TappableCopy);
38 changes: 25 additions & 13 deletions src/pages/DetailsPage.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React from 'react';
import {
View,
} from 'react-native';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
Expand All @@ -16,6 +14,8 @@ import ScreenWrapper from '../components/ScreenWrapper';
import personalDetailsPropType from './personalDetailsPropType';
import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
import compose from '../libs/compose';
import TappableCopy from '../components/TappableCopy';
import CONST from '../CONST';

const matchType = PropTypes.shape({
params: PropTypes.shape({
Expand Down Expand Up @@ -69,6 +69,7 @@ const DetailsPage = ({
const timezone = moment().tz(details.timezone.selected);
const GMTTime = `${timezone.toString().split(/[+-]/)[0].slice(-3)} ${timezone.zoneAbbr()}`;
const currentTime = Number.isNaN(Number(timezone.zoneAbbr())) ? timezone.zoneAbbr() : GMTTime;

return (
<ScreenWrapper>
<HeaderWithCloseButton
Expand All @@ -91,23 +92,34 @@ const DetailsPage = ({
imageStyles={[styles.avatarLarge]}
source={details.avatar}
/>
<Text style={[styles.displayName, styles.mt1, styles.mb6]} numberOfLines={1}>
{details.displayName && isSMSLogin
? toLocalPhone(details.displayName)
: (details.displayName || null)}
</Text>
<TappableCopy
style={[styles.mt1, styles.mb6]}
type={details.displayName && isSMSLogin ? CONST.LOGIN_TYPE.PHONE : undefined}
value={getPhoneNumber(details)}
>
<Text style={[styles.displayName]} numberOfLines={1}>
{details.displayName && isSMSLogin
? toLocalPhone(details.displayName)
: (details.displayName || null)}
</Text>
</TappableCopy>
{details.login ? (
<View style={[styles.mb6, styles.detailsPageSectionContainer]}>
<Text style={[styles.formLabel, styles.mb2]} numberOfLines={1}>
{translate(isSMSLogin
? 'common.phoneNumber'
: 'common.email')}
</Text>
<Text style={[styles.textP]} numberOfLines={1}>
{isSMSLogin
? toLocalPhone(getPhoneNumber(details))
: details.login}
</Text>
<TappableCopy
type={isSMSLogin ? CONST.LOGIN_TYPE.PHONE : CONST.LOGIN_TYPE.EMAIL}
value={isSMSLogin ? getPhoneNumber(details) : details.login}
>
<Text style={[styles.textP]} numberOfLines={1}>
{isSMSLogin
? toLocalPhone(getPhoneNumber(details))
: details.login}
</Text>
</TappableCopy>
</View>
) : null}
{details.pronouns ? (
Expand Down
6 changes: 3 additions & 3 deletions src/pages/home/report/ReportActionContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import getReportActionContextMenuStyles from '../../../styles/getReportActionCon
import {
setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft,
} from '../../../libs/actions/Report';
import ReportActionContextMenuItem from './ReportActionContextMenuItem';
import ContextMenuItem from '../../../components/ContextMenuItem';
import ReportActionPropTypes from './ReportActionPropTypes';
import Clipboard from '../../../libs/Clipboard';
import compose from '../../../libs/compose';
Expand Down Expand Up @@ -74,7 +74,7 @@ class ReportActionContextMenu extends React.Component {
shouldShow: true,

// If return value is true, we switch the `text` and `icon` on
// `ReportActionContextMenuItem` with `successText` and `successIcon` which will fallback to
// `ContextMenuItem` with `successText` and `successIcon` which will fallback to
// the `text` and `icon`
onPress: () => {
const message = _.last(lodashGet(this.props.reportAction, 'message', null));
Expand Down Expand Up @@ -179,7 +179,7 @@ class ReportActionContextMenu extends React.Component {
return this.props.isVisible && (
<View style={this.wrapperStyle}>
{this.contextActions.map(contextAction => _.result(contextAction, 'shouldShow', false) && (
<ReportActionContextMenuItem
<ContextMenuItem
icon={contextAction.icon}
text={contextAction.text}
successIcon={contextAction.successIcon}
Expand Down