diff --git a/lib/shared/relationship-utils.js b/lib/shared/relationship-utils.js index 956c6d133..fb98e9135 100644 --- a/lib/shared/relationship-utils.js +++ b/lib/shared/relationship-utils.js @@ -1,114 +1,114 @@ // @flow import invariant from 'invariant'; import { type RelationshipButton, type UserRelationshipStatus, userRelationshipStatus, relationshipButtons, relationshipActions, type RelationshipAction, } from '../types/relationship-types.js'; import type { UserInfo } from '../types/user-types.js'; function sortIDs(firstId: string, secondId: string): string[] { return [Number(firstId), Number(secondId)] .sort((a, b) => a - b) .map(num => num.toString()); } function getAvailableRelationshipButtons( userInfo: UserInfo, ): RelationshipButton[] { const relationship = userInfo.relationshipStatus; if (relationship === userRelationshipStatus.FRIEND) { return [relationshipButtons.UNFRIEND, relationshipButtons.BLOCK]; } else if (relationship === userRelationshipStatus.BLOCKED_VIEWER) { return [relationshipButtons.BLOCK]; } else if ( relationship === userRelationshipStatus.BOTH_BLOCKED || relationship === userRelationshipStatus.BLOCKED_BY_VIEWER ) { return [relationshipButtons.UNBLOCK]; } else if (relationship === userRelationshipStatus.REQUEST_RECEIVED) { return [ relationshipButtons.ACCEPT, relationshipButtons.REJECT, relationshipButtons.BLOCK, ]; } else if (relationship === userRelationshipStatus.REQUEST_SENT) { return [relationshipButtons.WITHDRAW, relationshipButtons.BLOCK]; } else { return [relationshipButtons.FRIEND, relationshipButtons.BLOCK]; } } function relationshipBlockedInEitherDirection( - relationshipStatus: UserRelationshipStatus, + relationshipStatus: ?UserRelationshipStatus, ): boolean { return ( relationshipStatus === userRelationshipStatus.BLOCKED_VIEWER || relationshipStatus === userRelationshipStatus.BLOCKED_BY_VIEWER || relationshipStatus === userRelationshipStatus.BOTH_BLOCKED ); } // ESLint doesn't recognize that invariant always throws // eslint-disable-next-line consistent-return function getRelationshipDispatchAction( relationshipButton: RelationshipButton, ): RelationshipAction { if (relationshipButton === relationshipButtons.BLOCK) { return relationshipActions.BLOCK; } else if ( relationshipButton === relationshipButtons.FRIEND || relationshipButton === relationshipButtons.ACCEPT ) { return relationshipActions.FRIEND; } else if ( relationshipButton === relationshipButtons.UNFRIEND || relationshipButton === relationshipButtons.REJECT || relationshipButton === relationshipButtons.WITHDRAW ) { return relationshipActions.UNFRIEND; } else if (relationshipButton === relationshipButtons.UNBLOCK) { return relationshipActions.UNBLOCK; } invariant(false, 'relationshipButton conditions should be exhaustive'); } // ESLint doesn't recognize that invariant always throws // eslint-disable-next-line consistent-return function getRelationshipActionText( relationshipButton: RelationshipButton, username: string, ): string { switch (relationshipButton) { case relationshipButtons.BLOCK: return `Block ${username}`; case relationshipButtons.FRIEND: return `Add ${username} to friends`; case relationshipButtons.UNFRIEND: return `Unfriend ${username}`; case relationshipButtons.UNBLOCK: return `Unblock ${username}`; case relationshipButtons.ACCEPT: return `Accept friend request from ${username}`; case relationshipButtons.REJECT: return `Reject friend request from ${username}`; case relationshipButtons.WITHDRAW: return `Withdraw request to friend ${username}`; default: invariant(false, 'invalid relationshipButton value'); } } export { sortIDs, getAvailableRelationshipButtons, relationshipBlockedInEitherDirection, getRelationshipDispatchAction, getRelationshipActionText, }; diff --git a/native/components/user-profile-message-button.react.js b/native/components/user-profile-message-button.react.js new file mode 100644 index 000000000..524fc3fd5 --- /dev/null +++ b/native/components/user-profile-message-button.react.js @@ -0,0 +1,69 @@ +// @flow + +import { useBottomSheetModal } from '@gorhom/bottom-sheet'; +import * as React from 'react'; +import { Text } from 'react-native'; + +import type { ThreadInfo } from 'lib/types/thread-types.js'; +import type { UserInfo } from 'lib/types/user-types'; + +import Button from './button.react.js'; +import SWMansionIcon from './swmansion-icon.react.js'; +import { useNavigateToThread } from '../chat/message-list-types.js'; +import { useStyles } from '../themes/colors.js'; + +type Props = { + +threadInfo: ThreadInfo, + +pendingPersonalThreadUserInfo?: UserInfo, +}; + +function UserProfileMessageButton(props: Props): React.Node { + const { threadInfo, pendingPersonalThreadUserInfo } = props; + + const { dismiss: dismissBottomSheetModal } = useBottomSheetModal(); + + const styles = useStyles(unboundStyles); + + const navigateToThread = useNavigateToThread(); + + const onPressMessage = React.useCallback(() => { + dismissBottomSheetModal(); + navigateToThread({ + threadInfo, + pendingPersonalThreadUserInfo, + }); + }, [ + dismissBottomSheetModal, + navigateToThread, + pendingPersonalThreadUserInfo, + threadInfo, + ]); + + return ( + + ); +} + +const unboundStyles = { + messageButtonContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'purpleButton', + paddingVertical: 8, + marginTop: 16, + borderRadius: 8, + }, + messageButtonIcon: { + color: 'floatingButtonLabel', + paddingRight: 8, + }, + messageButtonText: { + color: 'floatingButtonLabel', + }, +}; + +export default UserProfileMessageButton; diff --git a/native/components/user-profile.react.js b/native/components/user-profile.react.js index 4ce19b26f..1cf8fa47a 100644 --- a/native/components/user-profile.react.js +++ b/native/components/user-profile.react.js @@ -1,118 +1,157 @@ // @flow import Clipboard from '@react-native-clipboard/clipboard'; import * as React from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; +import { relationshipBlockedInEitherDirection } from 'lib/shared/relationship-utils.js'; +import { useUserProfileThreadInfo } from 'lib/shared/thread-utils.js'; import { stringForUserExplicit } from 'lib/shared/user-utils.js'; import type { UserInfo } from 'lib/types/user-types'; import sleep from 'lib/utils/sleep.js'; import SWMansionIcon from './swmansion-icon.react.js'; +import UserProfileMessageButton from './user-profile-message-button.react.js'; import UserAvatar from '../avatars/user-avatar.react.js'; import SingleLine from '../components/single-line.react.js'; import { useStyles } from '../themes/colors.js'; type Props = { +userInfo: ?UserInfo, }; function UserProfile(props: Props): React.Node { const { userInfo } = props; + const userProfileThreadInfo = useUserProfileThreadInfo(userInfo); + const usernameText = stringForUserExplicit(userInfo); const [usernameCopied, setUsernameCopied] = React.useState(false); const styles = useStyles(unboundStyles); const onPressCopyUsername = React.useCallback(async () => { Clipboard.setString(usernameText); setUsernameCopied(true); await sleep(3000); setUsernameCopied(false); }, [usernameText]); const copyUsernameButton = React.useMemo(() => { if (usernameCopied) { return ( Username copied! ); } return ( Copy username ); }, [ onPressCopyUsername, styles.copyUsernameContainer, styles.copyUsernameIcon, styles.copyUsernameText, usernameCopied, ]); + const messageButton = React.useMemo(() => { + if ( + !userProfileThreadInfo || + relationshipBlockedInEitherDirection(userInfo?.relationshipStatus) + ) { + return null; + } + + const { threadInfo, pendingPersonalThreadUserInfo } = userProfileThreadInfo; + return ( + + ); + }, [userInfo?.relationshipStatus, userProfileThreadInfo]); + return ( {usernameText} {copyUsernameButton} + {messageButton} ); } const unboundStyles = { container: { paddingHorizontal: 16, }, moreIcon: { color: 'modalButtonLabel', alignSelf: 'flex-end', }, userInfoContainer: { flexDirection: 'row', }, usernameContainer: { flex: 1, justifyContent: 'center', alignItems: 'flex-start', paddingLeft: 16, }, usernameText: { color: 'modalForegroundLabel', fontSize: 18, fontWeight: '500', }, copyUsernameContainer: { flexDirection: 'row', justifyContent: 'center', paddingTop: 8, }, copyUsernameIcon: { color: 'purpleLink', marginRight: 4, }, copyUsernameText: { color: 'purpleLink', fontSize: 12, }, + messageButtonContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'purpleButton', + paddingVertical: 8, + marginTop: 16, + borderRadius: 8, + }, + messageButtonIcon: { + color: 'floatingButtonLabel', + paddingRight: 8, + }, + messageButtonText: { + color: 'floatingButtonLabel', + }, }; export default UserProfile;