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;