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[] {