From 1448ee843a89e3c283910ba140e651aa4c6d7b8d Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 22 Mar 2022 14:08:49 -0300 Subject: [PATCH] Extract re-usable user_item component --- .../at_mention_item/at_mention_item.tsx | 175 ------------------ .../autocomplete/at_mention_item/index.tsx | 49 +++++ .../at_mention_item => user_item}/index.ts | 4 +- app/components/user_item/user_item.tsx | 174 +++++++++++++++++ app/utils/user/index.ts | 14 +- 5 files changed, 234 insertions(+), 182 deletions(-) delete mode 100644 app/components/autocomplete/at_mention_item/at_mention_item.tsx create mode 100644 app/components/autocomplete/at_mention_item/index.tsx rename app/components/{autocomplete/at_mention_item => user_item}/index.ts (92%) create mode 100644 app/components/user_item/user_item.tsx diff --git a/app/components/autocomplete/at_mention_item/at_mention_item.tsx b/app/components/autocomplete/at_mention_item/at_mention_item.tsx deleted file mode 100644 index b1227e778..000000000 --- a/app/components/autocomplete/at_mention_item/at_mention_item.tsx +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {useCallback} from 'react'; -import {Text, View} from 'react-native'; -import {useSafeAreaInsets} from 'react-native-safe-area-context'; - -import ChannelIcon from '@components/channel_icon'; -import CustomStatusEmoji from '@components/custom_status/custom_status_emoji'; -import FormattedText from '@components/formatted_text'; -import ProfilePicture from '@components/profile_picture'; -import {BotTag, GuestTag} from '@components/tag'; -import TouchableWithFeedback from '@components/touchable_with_feedback'; -import {General} from '@constants'; -import {useTheme} from '@context/theme'; -import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; -import {getUserCustomStatus, isGuest, isShared} from '@utils/user'; - -type AtMentionItemProps = { - user: UserProfile; - currentUserId: string; - onPress: (username: string) => void; - showFullName: boolean; - testID?: string; - isCustomStatusEnabled: boolean; -} - -const getName = (user: UserProfile, showFullName: boolean, isCurrentUser: boolean) => { - let name = ''; - const hasNickname = user.nickname.length > 0; - - if (showFullName) { - name += `${user.first_name} ${user.last_name} `; - } - - if (hasNickname && !isCurrentUser) { - name += name.length > 0 ? `(${user.nickname})` : user.nickname; - } - - return name.trim(); -}; - -const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => { - return { - row: { - height: 40, - paddingVertical: 8, - paddingTop: 4, - paddingHorizontal: 16, - flexDirection: 'row', - alignItems: 'center', - }, - rowPicture: { - marginRight: 10, - marginLeft: 2, - width: 24, - alignItems: 'center', - justifyContent: 'center', - }, - rowInfo: { - flexDirection: 'row', - overflow: 'hidden', - }, - rowFullname: { - fontSize: 15, - color: theme.centerChannelColor, - paddingLeft: 4, - flexShrink: 1, - }, - rowUsername: { - color: changeOpacity(theme.centerChannelColor, 0.56), - fontSize: 15, - flexShrink: 5, - }, - icon: { - marginLeft: 4, - }, - }; -}); - -const AtMentionItem = ({ - user, - currentUserId, - onPress, - showFullName, - testID, - isCustomStatusEnabled, -}: AtMentionItemProps) => { - const insets = useSafeAreaInsets(); - const theme = useTheme(); - const style = getStyleFromTheme(theme); - - const guest = isGuest(user.roles); - const shared = isShared(user); - - const completeMention = useCallback(() => { - onPress(user.username); - }, [user.username]); - - const isCurrentUser = currentUserId === user.id; - const name = getName(user, showFullName, isCurrentUser); - const customStatus = getUserCustomStatus(user); - - return ( - - - - - - - {Boolean(user.is_bot) && ()} - {guest && ()} - {Boolean(name.length) && ( - - {name} - - )} - {isCurrentUser && ( - - )} - - {` @${user.username}`} - - - {isCustomStatusEnabled && !user.is_bot && customStatus && ( - - )} - {shared && ( - - )} - - - ); -}; - -export default AtMentionItem; diff --git a/app/components/autocomplete/at_mention_item/index.tsx b/app/components/autocomplete/at_mention_item/index.tsx new file mode 100644 index 000000000..ecc062a5f --- /dev/null +++ b/app/components/autocomplete/at_mention_item/index.tsx @@ -0,0 +1,49 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback} from 'react'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; + +import TouchableWithFeedback from '@components/touchable_with_feedback'; +import UserItem from '@components/user_item'; +import {useTheme} from '@context/theme'; +import {changeOpacity} from '@utils/theme'; + +import type UserModel from '@typings/database/models/servers/user'; + +type AtMentionItemProps = { + user: UserProfile | UserModel; + onPress?: (username: string) => void; + testID?: string; +} + +const AtMentionItem = ({ + user, + onPress, + testID, +}: AtMentionItemProps) => { + const insets = useSafeAreaInsets(); + const theme = useTheme(); + + const completeMention = useCallback(() => { + onPress?.(user.username); + }, [user.username]); + + return ( + + + + ); +}; + +export default AtMentionItem; diff --git a/app/components/autocomplete/at_mention_item/index.ts b/app/components/user_item/index.ts similarity index 92% rename from app/components/autocomplete/at_mention_item/index.ts rename to app/components/user_item/index.ts index eed265068..af20dbdce 100644 --- a/app/components/autocomplete/at_mention_item/index.ts +++ b/app/components/user_item/index.ts @@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators'; import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database'; -import AtMentionItem from './at_mention_item'; +import UserItem from './user_item'; import type {WithDatabaseArgs} from '@typings/database/database'; import type SystemModel from '@typings/database/models/servers/system'; @@ -34,4 +34,4 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { }; }); -export default withDatabase(enhanced(AtMentionItem)); +export default withDatabase(enhanced(UserItem)); diff --git a/app/components/user_item/user_item.tsx b/app/components/user_item/user_item.tsx new file mode 100644 index 000000000..a6e91217f --- /dev/null +++ b/app/components/user_item/user_item.tsx @@ -0,0 +1,174 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {IntlShape, useIntl} from 'react-intl'; +import {StyleProp, Text, View, ViewStyle} from 'react-native'; + +import ChannelIcon from '@components/channel_icon'; +import CustomStatusEmoji from '@components/custom_status/custom_status_emoji'; +import FormattedText from '@components/formatted_text'; +import ProfilePicture from '@components/profile_picture'; +import {BotTag, GuestTag} from '@components/tag'; +import {General} from '@constants'; +import {useTheme} from '@context/theme'; +import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; +import {getUserCustomStatus, isBot, isGuest, isShared} from '@utils/user'; + +import type UserModel from '@typings/database/models/servers/user'; + +type AtMentionItemProps = { + user?: UserProfile | UserModel; + containerStyle?: StyleProp; + currentUserId: string; + showFullName: boolean; + testID?: string; + isCustomStatusEnabled: boolean; +} + +const getName = (user: UserProfile | UserModel | undefined, showFullName: boolean, isCurrentUser: boolean, intl: IntlShape) => { + let name = ''; + if (!user) { + return intl.formatMessage({id: 'channel_loader.someone', defaultMessage: 'Someone'}); + } + + const hasNickname = user.nickname.length > 0; + + if (showFullName) { + const first = 'first_name' in user ? user.first_name : user.firstName; + const last = 'last_name' in user ? user.last_name : user.lastName; + name += `${first} ${last} `; + } + + if (hasNickname && !isCurrentUser) { + name += name.length > 0 ? `(${user.nickname})` : user.nickname; + } + + return name.trim(); +}; + +const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => { + return { + row: { + height: 40, + paddingVertical: 8, + paddingTop: 4, + paddingHorizontal: 16, + flexDirection: 'row', + alignItems: 'center', + }, + rowPicture: { + marginRight: 10, + marginLeft: 2, + width: 24, + alignItems: 'center', + justifyContent: 'center', + }, + rowInfo: { + flexDirection: 'row', + overflow: 'hidden', + }, + rowFullname: { + fontSize: 15, + color: theme.centerChannelColor, + fontFamily: 'OpenSans', + paddingLeft: 4, + flexShrink: 1, + }, + rowUsername: { + color: changeOpacity(theme.centerChannelColor, 0.56), + fontSize: 15, + fontFamily: 'OpenSans', + flexShrink: 5, + }, + icon: { + marginLeft: 4, + }, + }; +}); + +const UserItem = ({ + containerStyle, + user, + currentUserId, + showFullName, + testID, + isCustomStatusEnabled, +}: AtMentionItemProps) => { + const theme = useTheme(); + const style = getStyleFromTheme(theme); + const intl = useIntl(); + + const bot = user ? isBot(user) : false; + const guest = user ? isGuest(user.roles) : false; + const shared = user ? isShared(user) : false; + + const isCurrentUser = currentUserId === user?.id; + const name = getName(user, showFullName, isCurrentUser, intl); + const customStatus = getUserCustomStatus(user); + + return ( + + + + + + {bot && } + {guest && } + {Boolean(name.length) && + + {name} + + } + {isCurrentUser && + + } + {Boolean(user) && + + {` @${user!.username}`} + + } + + {isCustomStatusEnabled && !bot && customStatus && ( + + )} + {shared && ( + + )} + + ); +}; + +export default UserItem; diff --git a/app/utils/user/index.ts b/app/utils/user/index.ts index 147e62744..e0e60b02e 100644 --- a/app/utils/user/index.ts +++ b/app/utils/user/index.ts @@ -123,13 +123,13 @@ export const getTimezone = (timezone: UserTimezone | null) => { return timezone.manualTimezone; }; -export const getUserCustomStatus = (user: UserModel | UserProfile): UserCustomStatus | undefined => { +export const getUserCustomStatus = (user?: UserModel | UserProfile): UserCustomStatus | undefined => { try { - if (typeof user.props?.customStatus === 'string') { + if (typeof user?.props?.customStatus === 'string') { return JSON.parse(user.props.customStatus) as UserCustomStatus; } - return user.props?.customStatus; + return user?.props?.customStatus; } catch { return undefined; } @@ -192,8 +192,12 @@ export function confirmOutOfOfficeDisabled(intl: IntlShape, status: string, upda ); } -export function isShared(user: UserProfile): boolean { - return Boolean(user.remote_id); +export function isBot(user: UserProfile | UserModel): boolean { + return 'is_bot' in user ? Boolean(user.is_bot) : Boolean(user.isBot); +} + +export function isShared(user: UserProfile | UserModel): boolean { + return 'remote_id' in user ? Boolean(user.remote_id) : Boolean(user.props?.remote_id); } export function removeUserFromList(userId: string, originalList: UserProfile[]): UserProfile[] {