diff --git a/native/user-profile/user-profile-avatar.react.js b/native/user-profile/user-profile-avatar.react.js new file mode 100644 index 000000000..c48e85217 --- /dev/null +++ b/native/user-profile/user-profile-avatar.react.js @@ -0,0 +1,73 @@ +// @flow + +import { useNavigation, useRoute } from '@react-navigation/native'; +import invariant from 'invariant'; +import * as React from 'react'; +import { View, TouchableOpacity } from 'react-native'; + +import { userProfileUserInfoContainerHeight } from './user-profile-constants.js'; +import UserAvatar from '../avatars/user-avatar.react.js'; +import { OverlayContext } from '../navigation/overlay-context.js'; +import { UserProfileAvatarModalRouteName } from '../navigation/route-names.js'; + +// We need to set onAvatarLayout in order to allow .measure() to be on the ref +const onAvatarLayout = () => {}; + +type Props = { + userID: ?string, +}; + +function UserProfileAvatar(props: Props): React.Node { + const { userID } = props; + + const { navigate } = useNavigation(); + const route = useRoute(); + + const overlayContext = React.useContext(OverlayContext); + + const avatarRef = React.useRef(); + + const onPressAvatar = React.useCallback(() => { + invariant(overlayContext, 'UserProfileAvatar should have OverlayContext'); + overlayContext.setScrollBlockingModalStatus('open'); + + const currentAvatarRef = avatarRef.current; + if (!currentAvatarRef) { + return; + } + + currentAvatarRef.measure((x, y, width, height, pageX, pageY) => { + const coordinates = { + x: pageX, + y: pageY, + width, + height, + }; + + const verticalBounds = { + height: userProfileUserInfoContainerHeight, + y: pageY, + }; + + navigate<'UserProfileAvatarModal'>({ + name: UserProfileAvatarModalRouteName, + params: { + presentedFrom: route.key, + initialCoordinates: coordinates, + verticalBounds, + userID, + }, + }); + }); + }, [navigate, overlayContext, route.key, userID]); + + return ( + + + + + + ); +} + +export default UserProfileAvatar; diff --git a/native/user-profile/user-profile.react.js b/native/user-profile/user-profile.react.js index 0661a2cd5..7582a2c7d 100644 --- a/native/user-profile/user-profile.react.js +++ b/native/user-profile/user-profile.react.js @@ -1,228 +1,228 @@ // @flow import Clipboard from '@react-native-clipboard/clipboard'; import invariant from 'invariant'; import * as React from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; 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 UserProfileAvatar from './user-profile-avatar.react.js'; import { userProfileUserInfoContainerHeight, userProfileBottomPadding, userProfileMenuButtonHeight, userProfileActionButtonHeight, } from './user-profile-constants.js'; import UserProfileMessageButton from './user-profile-message-button.react.js'; import UserProfileRelationshipButton from './user-profile-relationship-button.react.js'; -import UserAvatar from '../avatars/user-avatar.react.js'; import { BottomSheetContext } from '../bottom-sheet/bottom-sheet-provider.react.js'; import SingleLine from '../components/single-line.react.js'; import SWMansionIcon from '../components/swmansion-icon.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 [ userProfileRelationshipButtonHeight, setUserProfileRelationshipButtonHeight, ] = React.useState(0); const bottomSheetContext = React.useContext(BottomSheetContext); invariant(bottomSheetContext, 'bottomSheetContext should be set'); const { setContentHeight } = bottomSheetContext; const insets = useSafeAreaInsets(); React.useLayoutEffect(() => { let height = insets.bottom + userProfileUserInfoContainerHeight + userProfileBottomPadding; if (userProfileThreadInfo) { height += userProfileMenuButtonHeight; } if ( userProfileThreadInfo && !relationshipBlockedInEitherDirection(userInfo?.relationshipStatus) ) { // message button height + relationship button height height += userProfileActionButtonHeight + userProfileRelationshipButtonHeight; } setContentHeight(height); }, [ insets.bottom, setContentHeight, userInfo?.relationshipStatus, userProfileRelationshipButtonHeight, userProfileThreadInfo, ]); 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]); const relationshipButton = React.useMemo(() => { if ( !userProfileThreadInfo || relationshipBlockedInEitherDirection(userInfo?.relationshipStatus) ) { return null; } const { threadInfo, pendingPersonalThreadUserInfo } = userProfileThreadInfo; return ( ); }, [userInfo?.relationshipStatus, userProfileThreadInfo]); return ( - + {usernameText} {copyUsernameButton} {messageButton} {relationshipButton} ); } 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;