Extract re-usable user_item component
This commit is contained in:
parent
7431fd4120
commit
1448ee843a
5 changed files with 234 additions and 182 deletions
|
|
@ -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 (
|
||||
<TouchableWithFeedback
|
||||
testID={testID}
|
||||
key={user.id}
|
||||
onPress={completeMention}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
style={{marginLeft: insets.left, marginRight: insets.right}}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<View style={style.rowPicture}>
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={24}
|
||||
showStatus={false}
|
||||
testID='at_mention_item.profile_picture'
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={[style.rowInfo, {maxWidth: shared ? '75%' : '80%'}]}
|
||||
>
|
||||
{Boolean(user.is_bot) && (<BotTag/>)}
|
||||
{guest && (<GuestTag/>)}
|
||||
{Boolean(name.length) && (
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
testID='at_mention_item.name'
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
)}
|
||||
{isCurrentUser && (
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage=' (you)'
|
||||
style={style.rowUsername}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
numberOfLines={1}
|
||||
testID='at_mention_item.username'
|
||||
>
|
||||
{` @${user.username}`}
|
||||
</Text>
|
||||
</View>
|
||||
{isCustomStatusEnabled && !user.is_bot && customStatus && (
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus}
|
||||
style={style.icon}
|
||||
/>
|
||||
)}
|
||||
{shared && (
|
||||
<ChannelIcon
|
||||
name={name}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isInfo={true}
|
||||
isUnread={true}
|
||||
size={18}
|
||||
shared={true}
|
||||
type={General.DM_CHANNEL}
|
||||
style={style.icon}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
export default AtMentionItem;
|
||||
49
app/components/autocomplete/at_mention_item/index.tsx
Normal file
49
app/components/autocomplete/at_mention_item/index.tsx
Normal file
|
|
@ -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 (
|
||||
<TouchableWithFeedback
|
||||
testID={testID}
|
||||
key={user.id}
|
||||
onPress={completeMention}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
style={{marginLeft: insets.left, marginRight: insets.right}}
|
||||
type={'native'}
|
||||
>
|
||||
<UserItem
|
||||
user={user}
|
||||
testID='at_mention'
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
export default AtMentionItem;
|
||||
|
|
@ -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));
|
||||
174
app/components/user_item/user_item.tsx
Normal file
174
app/components/user_item/user_item.tsx
Normal file
|
|
@ -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<ViewStyle>;
|
||||
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 (
|
||||
<View style={[style.row, containerStyle]}>
|
||||
<View style={style.rowPicture}>
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={24}
|
||||
showStatus={false}
|
||||
testID={`${testID}.profile_picture`}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={[style.rowInfo, {maxWidth: shared ? '75%' : '80%'}]}
|
||||
>
|
||||
{bot && <BotTag/>}
|
||||
{guest && <GuestTag/>}
|
||||
{Boolean(name.length) &&
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.name`}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
}
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage=' (you)'
|
||||
style={style.rowUsername}
|
||||
/>
|
||||
}
|
||||
{Boolean(user) &&
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
numberOfLines={1}
|
||||
testID='at_mention_item.username'
|
||||
>
|
||||
{` @${user!.username}`}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
{isCustomStatusEnabled && !bot && customStatus && (
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus}
|
||||
style={style.icon}
|
||||
/>
|
||||
)}
|
||||
{shared && (
|
||||
<ChannelIcon
|
||||
name={name}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isInfo={true}
|
||||
isUnread={true}
|
||||
size={18}
|
||||
shared={true}
|
||||
type={General.DM_CHANNEL}
|
||||
style={style.icon}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserItem;
|
||||
|
|
@ -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[] {
|
||||
|
|
|
|||
Loading…
Reference in a new issue