forked from Ivasoft/mattermost-mobile
Fix and unify channel and user list items (#7175)
* Fix channel and user list items * Fixes on members and create a dm lists * Fix tutorial and ipad * Fix test * Address feedback * Several fixes on Android * Fix tests * Address feedback * Add more non breaking strings --------- Co-authored-by: Daniel Espino <danielespino@MacBook-Pro-de-Daniel.local>
This commit is contained in:
committed by
GitHub
parent
67f1a2f0c9
commit
a8ee3a1b5a
@@ -20,7 +20,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
height: 40,
|
||||
paddingVertical: 8,
|
||||
paddingTop: 4,
|
||||
paddingHorizontal: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
@@ -2,12 +2,8 @@
|
||||
// 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';
|
||||
|
||||
@@ -22,26 +18,16 @@ const AtMentionItem = ({
|
||||
onPress,
|
||||
testID,
|
||||
}: AtMentionItemProps) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
|
||||
const completeMention = useCallback(() => {
|
||||
onPress?.(user.username);
|
||||
}, [user.username]);
|
||||
const completeMention = useCallback((u: UserModel | UserProfile) => {
|
||||
onPress?.(u.username);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
key={user.id}
|
||||
onPress={completeMention}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
style={{marginLeft: insets.left, marginRight: insets.right}}
|
||||
type={'native'}
|
||||
>
|
||||
<UserItem
|
||||
user={user}
|
||||
testID={testID}
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
<UserItem
|
||||
user={user}
|
||||
testID={testID}
|
||||
onUserPress={completeMention}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
listStyle: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,16 +8,15 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
section: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
sectionText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
...typography('Body', 75, 'SemiBold'),
|
||||
textTransform: 'uppercase',
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
paddingTop: 16,
|
||||
|
||||
@@ -7,13 +7,16 @@ import {Platform, SectionList, SectionListData, SectionListRenderItemInfo, Style
|
||||
|
||||
import {searchChannels} from '@actions/remote/channel';
|
||||
import AutocompleteSectionHeader from '@components/autocomplete/autocomplete_section_header';
|
||||
import ChannelMentionItem from '@components/autocomplete/channel_mention_item';
|
||||
import ChannelItem from '@components/channel_item';
|
||||
import {General} from '@constants';
|
||||
import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from '@constants/autocomplete';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
import {t} from '@i18n';
|
||||
import {getUserById} from '@queries/servers/user';
|
||||
import {hasTrailingSpaces} from '@utils/helpers';
|
||||
import {getUserIdFromChannelName} from '@utils/user';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
@@ -138,6 +141,7 @@ type Props = {
|
||||
matchTerm: string;
|
||||
localChannels: ChannelModel[];
|
||||
teamId: string;
|
||||
currentUserId: string;
|
||||
}
|
||||
|
||||
const emptySections: Array<SectionListData<Channel>> = [];
|
||||
@@ -155,6 +159,7 @@ const ChannelMention = ({
|
||||
matchTerm,
|
||||
localChannels,
|
||||
teamId,
|
||||
currentUserId,
|
||||
}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
@@ -193,7 +198,22 @@ const ChannelMention = ({
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const completeMention = useCallback((mention: string) => {
|
||||
const completeMention = useCallback(async (c: ChannelModel | Channel) => {
|
||||
let mention = c.name;
|
||||
const teammateId = getUserIdFromChannelName(currentUserId, c.name);
|
||||
|
||||
if (c.type === General.DM_CHANNEL && teammateId) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const user = await getUserById(database, teammateId);
|
||||
if (user) {
|
||||
mention = `@${user.username}`;
|
||||
}
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
const mentionPart = value.substring(0, localCursorPosition);
|
||||
|
||||
let completedDraft: string;
|
||||
@@ -231,14 +251,16 @@ const ChannelMention = ({
|
||||
setSections(emptySections);
|
||||
setRemoteChannels(emptyChannels);
|
||||
latestSearchAt.current = Date.now();
|
||||
}, [value, localCursorPosition, isSearch]);
|
||||
}, [value, localCursorPosition, isSearch, currentUserId]);
|
||||
|
||||
const renderItem = useCallback(({item}: SectionListRenderItemInfo<Channel | ChannelModel>) => {
|
||||
return (
|
||||
<ChannelMentionItem
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
onPress={completeMention}
|
||||
testID='autocomplete.channel_mention_item'
|
||||
isOnCenterBg={true}
|
||||
showChannelName={true}
|
||||
/>
|
||||
);
|
||||
}, [completeMention]);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from '@constants/autocomplete';
|
||||
import {observeChannel, queryAllMyChannel, queryChannelsForAutocomplete} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
|
||||
|
||||
import ChannelMention from './channel_mention';
|
||||
|
||||
@@ -58,6 +58,7 @@ const emptyChannelList: ChannelModel[] = [];
|
||||
const withMembers = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
return {
|
||||
myMembers: queryAllMyChannel(database).observe(),
|
||||
currentUserId: observeCurrentUserId(database),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {Text, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
icon: {
|
||||
marginRight: 11,
|
||||
opacity: 0.56,
|
||||
},
|
||||
row: {
|
||||
paddingHorizontal: 16,
|
||||
height: 40,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
rowDisplayName: {
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rowName: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.56,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type Props = {
|
||||
channel: Channel | ChannelModel;
|
||||
displayName?: string;
|
||||
isBot: boolean;
|
||||
isGuest: boolean;
|
||||
onPress: (name?: string) => void;
|
||||
testID?: string;
|
||||
};
|
||||
|
||||
const ChannelMentionItem = ({
|
||||
channel,
|
||||
displayName,
|
||||
isBot,
|
||||
isGuest,
|
||||
onPress,
|
||||
testID,
|
||||
}: Props) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
|
||||
const completeMention = () => {
|
||||
if (isDMorGM(channel)) {
|
||||
onPress('@' + displayName?.replace(/ /g, ''));
|
||||
} else {
|
||||
onPress(channel.name);
|
||||
}
|
||||
};
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
const margins = useMemo(() => {
|
||||
return {marginLeft: insets.left, marginRight: insets.right};
|
||||
}, [insets]);
|
||||
const rowStyle = useMemo(() => {
|
||||
return [style.row, margins];
|
||||
}, [margins, style]);
|
||||
|
||||
let component;
|
||||
|
||||
const isArchived = ('delete_at' in channel ? channel.delete_at : channel.deleteAt) > 0;
|
||||
const channelMentionItemTestId = `${testID}.${channel.name}`;
|
||||
|
||||
if (isDMorGM(channel)) {
|
||||
if (!displayName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
component = (
|
||||
<TouchableWithFeedback
|
||||
key={channel.id}
|
||||
onPress={completeMention}
|
||||
style={rowStyle}
|
||||
testID={channelMentionItemTestId}
|
||||
type={'opacity'}
|
||||
>
|
||||
<Text
|
||||
style={style.rowDisplayName}
|
||||
testID={`${channelMentionItemTestId}.display_name`}
|
||||
>
|
||||
{'@' + displayName}
|
||||
</Text>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
testID={`${channelMentionItemTestId}.bot.tag`}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
testID={`${channelMentionItemTestId}.guest.tag`}
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
} else {
|
||||
component = (
|
||||
<TouchableWithFeedback
|
||||
key={channel.id}
|
||||
onPress={completeMention}
|
||||
style={margins}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
testID={channelMentionItemTestId}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<ChannelIcon
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
type={channel.type}
|
||||
isInfo={true}
|
||||
isArchived={isArchived}
|
||||
size={18}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={style.rowDisplayName}
|
||||
testID={`${channelMentionItemTestId}.display_name`}
|
||||
>
|
||||
{displayName}
|
||||
<Text
|
||||
style={style.rowName}
|
||||
testID={`${channelMentionItemTestId}.name`}
|
||||
>
|
||||
{` ~${channel.name}`}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
return component;
|
||||
};
|
||||
|
||||
export default ChannelMentionItem;
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {General} from '@constants';
|
||||
import {observeUser} from '@queries/servers/user';
|
||||
|
||||
import ChannelMentionItem from './channel_mention_item';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type OwnProps = {
|
||||
channel: Channel | ChannelModel;
|
||||
}
|
||||
|
||||
const enhanced = withObservables([], ({database, channel}: WithDatabaseArgs & OwnProps) => {
|
||||
let user = of$<UserModel | undefined>(undefined);
|
||||
const teammateId = 'teammate_id' in channel ? channel.teammate_id : '';
|
||||
const channelDisplayName = 'display_name' in channel ? channel.display_name : channel.displayName;
|
||||
if (channel.type === General.DM_CHANNEL && teammateId) {
|
||||
user = observeUser(database, teammateId!);
|
||||
}
|
||||
|
||||
const isBot = user.pipe(switchMap((u) => of$(u ? u.isBot : false)));
|
||||
const isGuest = user.pipe(switchMap((u) => of$(u ? u.isGuest : false)));
|
||||
const displayName = user.pipe(switchMap((u) => of$(u ? u.username : channelDisplayName)));
|
||||
|
||||
return {
|
||||
isBot,
|
||||
isGuest,
|
||||
displayName,
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhanced(ChannelMentionItem));
|
||||
@@ -54,7 +54,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 16,
|
||||
height: 40,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import {useIntl} from 'react-intl';
|
||||
import {FlatList, Platform, StyleProp, ViewStyle} from 'react-native';
|
||||
|
||||
import AtMentionItem from '@components/autocomplete/at_mention_item';
|
||||
import ChannelMentionItem from '@components/autocomplete/channel_mention_item';
|
||||
import ChannelItem from '@components/channel_item';
|
||||
import {COMMAND_SUGGESTION_CHANNEL, COMMAND_SUGGESTION_USER} from '@constants/apps';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import analytics from '@managers/analytics';
|
||||
@@ -98,7 +98,7 @@ const AppSlashSuggestion = ({
|
||||
}
|
||||
}, [serverUrl, updateValue]);
|
||||
|
||||
const completeIgnoringSuggestion = useCallback((base: string): (toIgnore: string) => void => {
|
||||
const completeIgnoringSuggestion = useCallback((base: string): () => void => {
|
||||
return () => {
|
||||
completeSuggestion(base);
|
||||
};
|
||||
@@ -127,10 +127,12 @@ const AppSlashSuggestion = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<ChannelMentionItem
|
||||
<ChannelItem
|
||||
channel={channel}
|
||||
onPress={completeIgnoringSuggestion(item.Complete)}
|
||||
testID='autocomplete.slash_suggestion.channel_mention_item'
|
||||
isOnCenterBg={true}
|
||||
showChannelName={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 16,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
slashIcon: {
|
||||
|
||||
@@ -18,12 +18,11 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
row: {
|
||||
height: 40,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 9,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
rowPicture: {
|
||||
marginHorizontal: 8,
|
||||
marginRight: 8,
|
||||
width: 20,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -143,7 +143,7 @@ function AutoCompleteSelector({
|
||||
|
||||
const goToSelectorScreen = useCallback(preventDoubleTap(() => {
|
||||
const screen = Screens.INTEGRATION_SELECTOR;
|
||||
goToScreen(screen, title, {dataSource, handleSelect, options, getDynamicOptions, selected, isMultiselect, teammateNameDisplay});
|
||||
goToScreen(screen, title, {dataSource, handleSelect, options, getDynamicOptions, selected, isMultiselect});
|
||||
}), [dataSource, options, getDynamicOptions]);
|
||||
|
||||
const handleSelect = useCallback((newSelection?: Selection) => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
@@ -10,54 +9,55 @@ import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {StyleProp, ViewStyle} from 'react-native';
|
||||
|
||||
type Props = {
|
||||
author?: UserModel;
|
||||
isInfo?: boolean;
|
||||
isOnCenterBg?: boolean;
|
||||
style: StyleProp<ViewStyle>;
|
||||
size: number;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {marginLeft: 4},
|
||||
status: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
borderWidth: 0,
|
||||
},
|
||||
statusInfo: {
|
||||
statusOnCenterBg: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.sidebarText, 0.4),
|
||||
left: 1,
|
||||
},
|
||||
iconInfo: {
|
||||
iconOnCenterBg: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||
},
|
||||
}));
|
||||
|
||||
const DmAvatar = ({author, isInfo}: Props) => {
|
||||
const DmAvatar = ({author, isOnCenterBg, style, size}: Props) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
if (author?.deleteAt) {
|
||||
return (
|
||||
<CompassIcon
|
||||
name='archive-outline'
|
||||
style={[style.icon, isInfo && style.iconInfo]}
|
||||
style={[styles.icon, style, isOnCenterBg && styles.iconOnCenterBg]}
|
||||
size={24}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<ProfilePicture
|
||||
author={author}
|
||||
size={24}
|
||||
showStatus={true}
|
||||
statusSize={12}
|
||||
statusStyle={[style.status, isInfo && style.statusInfo]}
|
||||
/>
|
||||
</View>
|
||||
<ProfilePicture
|
||||
author={author}
|
||||
size={size}
|
||||
showStatus={true}
|
||||
statusSize={12}
|
||||
statusStyle={[styles.status, isOnCenterBg && styles.statusOnCenterBg]}
|
||||
containerStyle={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ type ChannelIconProps = {
|
||||
hasDraft?: boolean;
|
||||
isActive?: boolean;
|
||||
isArchived?: boolean;
|
||||
isInfo?: boolean;
|
||||
isOnCenterBg?: boolean;
|
||||
isUnread?: boolean;
|
||||
isMuted?: boolean;
|
||||
membersCount?: number;
|
||||
@@ -43,10 +43,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
iconUnread: {
|
||||
color: theme.sidebarUnreadText,
|
||||
},
|
||||
iconInfo: {
|
||||
iconOnCenterBg: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||
},
|
||||
iconInfoUnread: {
|
||||
iconUnreadOnCenterBg: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
groupBox: {
|
||||
@@ -61,7 +61,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
groupBoxUnread: {
|
||||
backgroundColor: changeOpacity(theme.sidebarUnreadText, 0.3),
|
||||
},
|
||||
groupBoxInfo: {
|
||||
groupBoxOnCenterBg: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.3),
|
||||
},
|
||||
group: {
|
||||
@@ -74,10 +74,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
groupUnread: {
|
||||
color: theme.sidebarUnreadText,
|
||||
},
|
||||
groupInfo: {
|
||||
groupOnCenterBg: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||
},
|
||||
groupInfoUnread: {
|
||||
groupUnreadOnCenterBg: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
muted: {
|
||||
@@ -88,7 +88,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
||||
const ChannelIcon = ({
|
||||
hasDraft = false, isActive = false, isArchived = false,
|
||||
isInfo = false, isUnread = false, isMuted = false,
|
||||
isOnCenterBg = false, isUnread = false, isMuted = false,
|
||||
membersCount = 0, name,
|
||||
shared, size = 12, style, testID, type,
|
||||
}: ChannelIconProps) => {
|
||||
@@ -115,22 +115,38 @@ const ChannelIcon = ({
|
||||
activeGroup = styles.groupActive;
|
||||
}
|
||||
|
||||
if (isInfo) {
|
||||
activeIcon = isUnread && !isMuted ? styles.iconInfoUnread : styles.iconInfo;
|
||||
activeGroupBox = styles.groupBoxInfo;
|
||||
activeGroup = isUnread ? styles.groupInfoUnread : styles.groupInfo;
|
||||
if (isOnCenterBg) {
|
||||
activeIcon = isUnread && !isMuted ? styles.iconUnreadOnCenterBg : styles.iconOnCenterBg;
|
||||
activeGroupBox = styles.groupBoxOnCenterBg;
|
||||
activeGroup = isUnread ? styles.groupUnreadOnCenterBg : styles.groupOnCenterBg;
|
||||
}
|
||||
|
||||
if (isMuted) {
|
||||
mutedStyle = styles.muted;
|
||||
}
|
||||
|
||||
let icon;
|
||||
const commonStyles = [
|
||||
style,
|
||||
mutedStyle,
|
||||
];
|
||||
|
||||
const commonIconStyles = [
|
||||
styles.icon,
|
||||
unreadIcon,
|
||||
activeIcon,
|
||||
commonStyles,
|
||||
{fontSize: size},
|
||||
];
|
||||
|
||||
let icon = null;
|
||||
if (isArchived) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='archive-outline'
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 1}]}
|
||||
style={[
|
||||
commonIconStyles,
|
||||
{left: 1},
|
||||
]}
|
||||
testID={`${testID}.archive`}
|
||||
/>
|
||||
);
|
||||
@@ -138,7 +154,10 @@ const ChannelIcon = ({
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='pencil-outline'
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 2}]}
|
||||
style={[
|
||||
commonIconStyles,
|
||||
{left: 2},
|
||||
]}
|
||||
testID={`${testID}.draft`}
|
||||
/>
|
||||
);
|
||||
@@ -148,7 +167,10 @@ const ChannelIcon = ({
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
|
||||
style={[
|
||||
commonIconStyles,
|
||||
{left: 0.5},
|
||||
]}
|
||||
testID={sharedTestID}
|
||||
/>
|
||||
);
|
||||
@@ -156,7 +178,10 @@ const ChannelIcon = ({
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='globe'
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 1}]}
|
||||
style={[
|
||||
commonIconStyles,
|
||||
{left: 1},
|
||||
]}
|
||||
testID={`${testID}.public`}
|
||||
/>
|
||||
);
|
||||
@@ -164,7 +189,10 @@ const ChannelIcon = ({
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='lock-outline'
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
|
||||
style={[
|
||||
commonIconStyles,
|
||||
{left: 0.5},
|
||||
]}
|
||||
testID={`${testID}.private`}
|
||||
/>
|
||||
);
|
||||
@@ -172,7 +200,7 @@ const ChannelIcon = ({
|
||||
const fontSize = size - 12;
|
||||
icon = (
|
||||
<View
|
||||
style={[styles.groupBox, unreadGroupBox, activeGroupBox, {width: size, height: size}]}
|
||||
style={[styles.groupBox, unreadGroupBox, activeGroupBox, commonStyles, {width: size, height: size}]}
|
||||
>
|
||||
<Text
|
||||
style={[styles.group, unreadGroup, activeGroup, {fontSize}]}
|
||||
@@ -186,16 +214,14 @@ const ChannelIcon = ({
|
||||
icon = (
|
||||
<DmAvatar
|
||||
channelName={name}
|
||||
isInfo={isInfo}
|
||||
isOnCenterBg={isOnCenterBg}
|
||||
style={commonStyles}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, {width: size, height: size}, style, mutedStyle]}>
|
||||
{icon}
|
||||
</View>
|
||||
);
|
||||
return icon;
|
||||
};
|
||||
|
||||
export default React.memo(ChannelIcon);
|
||||
|
||||
@@ -41,11 +41,10 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
@@ -53,79 +52,71 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
}
|
||||
testID="channel_item.hello"
|
||||
>
|
||||
<Icon
|
||||
name="globe"
|
||||
style={
|
||||
[
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
"marginRight": 12,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
{
|
||||
"fontSize": 24,
|
||||
},
|
||||
],
|
||||
{
|
||||
"left": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.public"
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
[
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
},
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
],
|
||||
{
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="channel_item.hello.display_name"
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
{
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="globe"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
"fontSize": 24,
|
||||
"left": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.public"
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
]
|
||||
}
|
||||
testID="channel_item.hello.display_name"
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
@@ -171,11 +162,10 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
@@ -183,103 +173,93 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
}
|
||||
testID="channel_item.hello"
|
||||
>
|
||||
<Icon
|
||||
name="pencil-outline"
|
||||
style={
|
||||
[
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
"marginRight": 12,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
{
|
||||
"fontSize": 24,
|
||||
},
|
||||
],
|
||||
{
|
||||
"left": 2,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.draft"
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
[
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
},
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
],
|
||||
{
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="channel_item.hello.display_name"
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
{
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="pencil-outline"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
"fontSize": 24,
|
||||
"left": 2,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.draft"
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
]
|
||||
}
|
||||
testID="channel_item.hello.display_name"
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
/>
|
||||
<Icon
|
||||
name="phone-in-talk"
|
||||
size={16}
|
||||
style={
|
||||
[
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
},
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
],
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"paddingRight": 0,
|
||||
"textAlign": "right",
|
||||
},
|
||||
]
|
||||
@@ -330,11 +310,10 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
@@ -342,79 +321,71 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
}
|
||||
testID="channel_item.hello"
|
||||
>
|
||||
<Icon
|
||||
name="pencil-outline"
|
||||
style={
|
||||
[
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
"marginRight": 12,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
{
|
||||
"fontSize": 24,
|
||||
},
|
||||
],
|
||||
{
|
||||
"left": 2,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.draft"
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
[
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
},
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
],
|
||||
{
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="channel_item.hello.display_name"
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
{
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="pencil-outline"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
"fontSize": 24,
|
||||
"left": 2,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.draft"
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
]
|
||||
}
|
||||
testID="channel_item.hello.display_name"
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
125
app/components/channel_item/channel_body.tsx
Normal file
125
app/components/channel_item/channel_body.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import {StyleProp, Text, TextStyle, View} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {nonBreakingString} from '@utils/strings';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import CustomStatus from './custom_status';
|
||||
|
||||
type Props = {
|
||||
displayName: string;
|
||||
teamDisplayName: string;
|
||||
teammateId?: string;
|
||||
isMuted: boolean;
|
||||
textStyles: StyleProp<TextStyle>;
|
||||
testId: string;
|
||||
channelName: string;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
teamName: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 75),
|
||||
},
|
||||
teamNameMuted: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.32),
|
||||
},
|
||||
flex: {
|
||||
flex: 0,
|
||||
flexShrink: 1,
|
||||
},
|
||||
channelName: {
|
||||
...typography('Body', 200),
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
},
|
||||
customStatus: {
|
||||
marginLeft: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
export const ChannelBody = ({
|
||||
displayName,
|
||||
channelName,
|
||||
teamDisplayName,
|
||||
teammateId,
|
||||
isMuted,
|
||||
textStyles,
|
||||
testId,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const isTablet = useIsTablet();
|
||||
const nonBreakingDisplayName = nonBreakingString(displayName);
|
||||
const channelText = (
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={[textStyles, styles.flex]}
|
||||
testID={`${testId}.display_name`}
|
||||
>
|
||||
{nonBreakingDisplayName}
|
||||
{Boolean(channelName) && (
|
||||
<Text style={styles.channelName}>
|
||||
{nonBreakingString(` ~${channelName}`)}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
if (teamDisplayName) {
|
||||
const teamText = (
|
||||
<Text
|
||||
ellipsizeMode={isTablet ? undefined : 'tail'} // Handled by the parent text on tablets
|
||||
numberOfLines={isTablet ? undefined : 1} // Handled by the parent text on tablets
|
||||
style={[styles.teamName, isMuted && styles.teamNameMuted, styles.flex]}
|
||||
testID={`${testId}.team_display_name`}
|
||||
>
|
||||
{nonBreakingString(`${isTablet ? ' ' : ''}${teamDisplayName}`)}
|
||||
</Text>
|
||||
);
|
||||
|
||||
if (isTablet) {
|
||||
return (
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={[textStyles, styles.flex]}
|
||||
testID={`${testId}.display_name`}
|
||||
>
|
||||
{nonBreakingDisplayName}
|
||||
{teamText}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.flex}>
|
||||
{channelText}
|
||||
{teamText}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (teammateId) {
|
||||
const customStatus = (
|
||||
<CustomStatus
|
||||
userId={teammateId}
|
||||
style={styles.customStatus}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{channelText}
|
||||
{customStatus}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return channelText;
|
||||
};
|
||||
@@ -41,7 +41,6 @@ describe('components/channel_list/categories/body/channel_item', () => {
|
||||
onPress={() => undefined}
|
||||
isUnread={myChannel.isUnread}
|
||||
mentionsCount={myChannel.mentionsCount}
|
||||
hasMember={Boolean(myChannel)}
|
||||
hasCall={false}
|
||||
/>,
|
||||
);
|
||||
@@ -62,7 +61,6 @@ describe('components/channel_list/categories/body/channel_item', () => {
|
||||
onPress={() => undefined}
|
||||
isUnread={myChannel.isUnread}
|
||||
mentionsCount={myChannel.mentionsCount}
|
||||
hasMember={Boolean(myChannel)}
|
||||
hasCall={false}
|
||||
/>,
|
||||
);
|
||||
@@ -83,7 +81,6 @@ describe('components/channel_list/categories/body/channel_item', () => {
|
||||
onPress={() => undefined}
|
||||
isUnread={myChannel.isUnread}
|
||||
mentionsCount={myChannel.mentionsCount}
|
||||
hasMember={Boolean(myChannel)}
|
||||
hasCall={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -3,85 +3,79 @@
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import Badge from '@components/badge';
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {General} from '@constants';
|
||||
import {HOME_PADDING} from '@constants/view';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {getUserIdFromChannelName} from '@utils/user';
|
||||
|
||||
import CustomStatus from './custom_status';
|
||||
import {ChannelBody} from './channel_body';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
type Props = {
|
||||
channel: ChannelModel;
|
||||
channel: ChannelModel | Channel;
|
||||
currentUserId: string;
|
||||
hasDraft: boolean;
|
||||
isActive: boolean;
|
||||
isInfo?: boolean;
|
||||
isMuted: boolean;
|
||||
membersCount: number;
|
||||
isUnread: boolean;
|
||||
mentionsCount: number;
|
||||
onPress: (channelId: string) => void;
|
||||
hasMember: boolean;
|
||||
onPress: (channel: ChannelModel | Channel) => void;
|
||||
teamDisplayName?: string;
|
||||
testID?: string;
|
||||
hasCall: boolean;
|
||||
isOnCenterBg?: boolean;
|
||||
showChannelName?: boolean;
|
||||
isOnHome?: boolean;
|
||||
}
|
||||
|
||||
export const ROW_HEIGHT = 40;
|
||||
export const ROW_HEIGHT_WITH_TEAM = 58;
|
||||
|
||||
export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 20,
|
||||
minHeight: 40,
|
||||
alignItems: 'center',
|
||||
},
|
||||
infoItem: {
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
icon: {
|
||||
fontSize: 24,
|
||||
lineHeight: 28,
|
||||
color: changeOpacity(theme.sidebarText, 0.72),
|
||||
marginRight: 12,
|
||||
},
|
||||
text: {
|
||||
marginTop: -1,
|
||||
color: changeOpacity(theme.sidebarText, 0.72),
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
},
|
||||
highlight: {
|
||||
color: theme.sidebarUnreadText,
|
||||
},
|
||||
textInfo: {
|
||||
textOnCenterBg: {
|
||||
color: theme.centerChannelColor,
|
||||
paddingRight: 20,
|
||||
},
|
||||
muted: {
|
||||
color: changeOpacity(theme.sidebarText, 0.32),
|
||||
},
|
||||
mutedInfo: {
|
||||
mutedOnCenterBg: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.32),
|
||||
},
|
||||
badge: {
|
||||
borderColor: theme.sidebarBg,
|
||||
position: 'relative',
|
||||
left: 0,
|
||||
top: -2,
|
||||
marginLeft: 4,
|
||||
|
||||
//Overwrite default badge styles
|
||||
position: undefined,
|
||||
top: undefined,
|
||||
left: undefined,
|
||||
alignSelf: undefined,
|
||||
},
|
||||
infoBadge: {
|
||||
badgeOnCenterBg: {
|
||||
color: theme.buttonColor,
|
||||
backgroundColor: theme.buttonBg,
|
||||
borderColor: theme.centerChannelBg,
|
||||
@@ -93,31 +87,15 @@ export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.1),
|
||||
borderLeftColor: theme.sidebarTextActiveBorder,
|
||||
borderLeftWidth: 5,
|
||||
marginLeft: 0,
|
||||
paddingLeft: 14,
|
||||
},
|
||||
textActive: {
|
||||
color: theme.sidebarText,
|
||||
},
|
||||
teamName: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
paddingLeft: 12,
|
||||
marginTop: 4,
|
||||
...typography('Body', 75),
|
||||
},
|
||||
teamNameMuted: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.32),
|
||||
},
|
||||
teamNameTablet: {
|
||||
marginLeft: -12,
|
||||
paddingLeft: 0,
|
||||
marginTop: 0,
|
||||
paddingBottom: 0,
|
||||
top: 5,
|
||||
},
|
||||
hasCall: {
|
||||
textAlign: 'right',
|
||||
paddingRight: 0,
|
||||
},
|
||||
filler: {
|
||||
flex: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -126,137 +104,118 @@ export const textStyle = StyleSheet.create({
|
||||
regular: typography('Body', 200, 'Regular'),
|
||||
});
|
||||
|
||||
const ChannelListItem = ({
|
||||
channel, currentUserId, hasDraft,
|
||||
isActive, isInfo, isMuted, membersCount, hasMember,
|
||||
isUnread, mentionsCount, onPress, teamDisplayName, testID, hasCall}: Props) => {
|
||||
const ChannelItem = ({
|
||||
channel,
|
||||
currentUserId,
|
||||
hasDraft,
|
||||
isActive,
|
||||
isMuted,
|
||||
membersCount,
|
||||
isUnread,
|
||||
mentionsCount,
|
||||
onPress,
|
||||
teamDisplayName = '',
|
||||
testID,
|
||||
hasCall,
|
||||
isOnCenterBg = false,
|
||||
showChannelName = false,
|
||||
isOnHome = false,
|
||||
}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const channelName = (showChannelName && !isDMorGM(channel)) ? channel.name : '';
|
||||
|
||||
// Make it bolded if it has unreads or mentions
|
||||
const isBolded = isUnread || mentionsCount > 0;
|
||||
const showActive = isActive && isTablet;
|
||||
|
||||
const teammateId = (channel.type === General.DM_CHANNEL) ? getUserIdFromChannelName(currentUserId, channel.name) : undefined;
|
||||
const isOwnDirectMessage = (channel.type === General.DM_CHANNEL) && currentUserId === teammateId;
|
||||
|
||||
let displayName = 'displayName' in channel ? channel.displayName : channel.display_name;
|
||||
if (isOwnDirectMessage) {
|
||||
displayName = formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName});
|
||||
}
|
||||
|
||||
const deleteAt = 'deleteAt' in channel ? channel.deleteAt : channel.delete_at;
|
||||
const channelItemTestId = `${testID}.${channel.name}`;
|
||||
|
||||
const height = useMemo(() => {
|
||||
let h = 40;
|
||||
if (isInfo) {
|
||||
h = (teamDisplayName && !isTablet) ? 58 : 44;
|
||||
}
|
||||
return h;
|
||||
}, [teamDisplayName, isInfo, isTablet]);
|
||||
return (teamDisplayName && !isTablet) ? ROW_HEIGHT_WITH_TEAM : ROW_HEIGHT;
|
||||
}, [teamDisplayName, isTablet]);
|
||||
|
||||
const handleOnPress = useCallback(() => {
|
||||
onPress(channel.id);
|
||||
onPress(channel);
|
||||
}, [channel.id]);
|
||||
|
||||
const textStyles = useMemo(() => [
|
||||
isBolded && !isMuted ? textStyle.bold : textStyle.regular,
|
||||
styles.text,
|
||||
isBolded && styles.highlight,
|
||||
isActive && isTablet && !isInfo ? styles.textActive : null,
|
||||
isInfo ? styles.textInfo : null,
|
||||
showActive ? styles.textActive : null,
|
||||
isOnCenterBg ? styles.textOnCenterBg : null,
|
||||
isMuted && styles.muted,
|
||||
isMuted && isInfo && styles.mutedInfo,
|
||||
], [isBolded, styles, isMuted, isActive, isInfo, isTablet]);
|
||||
isMuted && isOnCenterBg && styles.mutedOnCenterBg,
|
||||
], [isBolded, styles, isMuted, showActive, isOnCenterBg]);
|
||||
|
||||
const containerStyle = useMemo(() => [
|
||||
styles.container,
|
||||
isActive && isTablet && !isInfo && styles.activeItem,
|
||||
isInfo && styles.infoItem,
|
||||
isOnHome && HOME_PADDING,
|
||||
showActive && styles.activeItem,
|
||||
showActive && isOnHome && {
|
||||
paddingLeft: HOME_PADDING.paddingLeft - styles.activeItem.borderLeftWidth,
|
||||
},
|
||||
{minHeight: height},
|
||||
],
|
||||
[height, isActive, isTablet, isInfo, styles]);
|
||||
|
||||
if (!hasMember) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const teammateId = (channel.type === General.DM_CHANNEL) ? getUserIdFromChannelName(currentUserId, channel.name) : undefined;
|
||||
const isOwnDirectMessage = (channel.type === General.DM_CHANNEL) && currentUserId === teammateId;
|
||||
|
||||
let displayName = channel.displayName;
|
||||
if (isOwnDirectMessage) {
|
||||
displayName = formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName});
|
||||
}
|
||||
|
||||
const channelItemTestId = `${testID}.${channel.name}`;
|
||||
], [height, showActive, styles, isOnHome]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={handleOnPress}>
|
||||
<>
|
||||
<View
|
||||
style={containerStyle}
|
||||
testID={channelItemTestId}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<ChannelIcon
|
||||
hasDraft={hasDraft}
|
||||
isActive={isInfo ? false : isTablet && isActive}
|
||||
isInfo={isInfo}
|
||||
isUnread={isBolded}
|
||||
isArchived={channel.deleteAt > 0}
|
||||
membersCount={membersCount}
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
size={24}
|
||||
type={channel.type}
|
||||
isMuted={isMuted}
|
||||
/>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={textStyles}
|
||||
testID={`${channelItemTestId}.display_name`}
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
{isInfo && Boolean(teamDisplayName) && !isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={[styles.teamName, isMuted && styles.teamNameMuted]}
|
||||
testID={`${channelItemTestId}.team_display_name`}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
{Boolean(teammateId) &&
|
||||
<CustomStatus
|
||||
isInfo={isInfo}
|
||||
testID={channelItemTestId}
|
||||
userId={teammateId!}
|
||||
/>
|
||||
}
|
||||
{isInfo && Boolean(teamDisplayName) && isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={[styles.teamName, styles.teamNameTablet, isMuted && styles.teamNameMuted]}
|
||||
testID={`${channelItemTestId}.team_display_name`}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
<Badge
|
||||
visible={mentionsCount > 0}
|
||||
value={mentionsCount}
|
||||
style={[styles.badge, isMuted && styles.mutedBadge, isInfo && styles.infoBadge]}
|
||||
/>
|
||||
{hasCall &&
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={16}
|
||||
style={[...textStyles, styles.hasCall]}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</>
|
||||
<View
|
||||
style={containerStyle}
|
||||
testID={channelItemTestId}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={hasDraft}
|
||||
isActive={isTablet && isActive}
|
||||
isOnCenterBg={isOnCenterBg}
|
||||
isUnread={isBolded}
|
||||
isArchived={deleteAt > 0}
|
||||
membersCount={membersCount}
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
size={24}
|
||||
type={channel.type}
|
||||
isMuted={isMuted}
|
||||
style={styles.icon}
|
||||
/>
|
||||
<ChannelBody
|
||||
displayName={displayName}
|
||||
isMuted={isMuted}
|
||||
teamDisplayName={teamDisplayName}
|
||||
teammateId={teammateId}
|
||||
testId={channelItemTestId}
|
||||
textStyles={textStyles}
|
||||
channelName={channelName}
|
||||
/>
|
||||
<View style={styles.filler}/>
|
||||
<Badge
|
||||
visible={mentionsCount > 0}
|
||||
value={mentionsCount}
|
||||
style={[styles.badge, isMuted && styles.mutedBadge, isOnCenterBg && styles.badgeOnCenterBg]}
|
||||
/>
|
||||
{hasCall &&
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={16}
|
||||
style={[textStyles, styles.hasCall]}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelListItem;
|
||||
export default ChannelItem;
|
||||
|
||||
@@ -2,30 +2,20 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
import CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
|
||||
|
||||
import type {EmojiCommonStyle} from '@typings/components/emoji';
|
||||
import type {StyleProp} from 'react-native';
|
||||
|
||||
type Props = {
|
||||
customStatus?: UserCustomStatus;
|
||||
customStatusExpired: boolean;
|
||||
isCustomStatusEnabled: boolean;
|
||||
isInfo?: boolean;
|
||||
testID?: string;
|
||||
style: StyleProp<EmojiCommonStyle>;
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
customStatusEmoji: {
|
||||
color: '#000',
|
||||
marginHorizontal: -5,
|
||||
top: 3,
|
||||
},
|
||||
info: {
|
||||
marginHorizontal: -15,
|
||||
},
|
||||
});
|
||||
|
||||
const CustomStatus = ({customStatus, customStatusExpired, isCustomStatusEnabled, isInfo, testID}: Props) => {
|
||||
const CustomStatus = ({customStatus, customStatusExpired, isCustomStatusEnabled, style}: Props) => {
|
||||
const showCustomStatusEmoji = Boolean(isCustomStatusEnabled && customStatus?.emoji && !customStatusExpired);
|
||||
|
||||
if (!showCustomStatusEmoji) {
|
||||
@@ -35,8 +25,7 @@ const CustomStatus = ({customStatus, customStatusExpired, isCustomStatusEnabled,
|
||||
return (
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus!}
|
||||
style={[style.customStatusEmoji, isInfo && style.info]}
|
||||
testID={testID}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,66 +21,68 @@ import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
type EnhanceProps = WithDatabaseArgs & {
|
||||
channel: ChannelModel;
|
||||
channel: ChannelModel | Channel;
|
||||
showTeamName?: boolean;
|
||||
serverUrl?: string;
|
||||
shouldHighlightActive?: boolean;
|
||||
shouldHighlightState?: boolean;
|
||||
}
|
||||
|
||||
const enhance = withObservables(['channel', 'showTeamName'], ({
|
||||
const enhance = withObservables(['channel', 'showTeamName', 'shouldHighlightActive', 'shouldHighlightState'], ({
|
||||
channel,
|
||||
database,
|
||||
showTeamName,
|
||||
serverUrl,
|
||||
showTeamName = false,
|
||||
shouldHighlightActive = false,
|
||||
shouldHighlightState = false,
|
||||
}: EnhanceProps) => {
|
||||
const currentUserId = observeCurrentUserId(database);
|
||||
const myChannel = observeMyChannel(database, channel.id);
|
||||
|
||||
const hasDraft = queryDraft(database, channel.id).observeWithColumns(['message', 'files']).pipe(
|
||||
switchMap((draft) => of$(draft.length > 0)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const hasDraft = shouldHighlightState ?
|
||||
queryDraft(database, channel.id).observeWithColumns(['message', 'files']).pipe(
|
||||
switchMap((draft) => of$(draft.length > 0)),
|
||||
distinctUntilChanged(),
|
||||
) : of$(false);
|
||||
|
||||
const isActive = observeCurrentChannelId(database).pipe(
|
||||
switchMap((id) => of$(id ? id === channel.id : false)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const isActive = shouldHighlightActive ?
|
||||
observeCurrentChannelId(database).pipe(
|
||||
switchMap((id) => of$(id ? id === channel.id : false)),
|
||||
distinctUntilChanged(),
|
||||
) : of$(false);
|
||||
|
||||
const isMuted = myChannel.pipe(
|
||||
switchMap((mc) => {
|
||||
if (!mc) {
|
||||
return of$(false);
|
||||
}
|
||||
return observeIsMutedSetting(database, mc.id);
|
||||
}),
|
||||
);
|
||||
const isMuted = shouldHighlightState ?
|
||||
myChannel.pipe(
|
||||
switchMap((mc) => {
|
||||
if (!mc) {
|
||||
return of$(false);
|
||||
}
|
||||
return observeIsMutedSetting(database, mc.id);
|
||||
}),
|
||||
) : of$(false);
|
||||
|
||||
let teamDisplayName = of$('');
|
||||
if (channel.teamId && showTeamName) {
|
||||
teamDisplayName = observeTeam(database, channel.teamId).pipe(
|
||||
const teamId = 'teamId' in channel ? channel.teamId : channel.team_id;
|
||||
const teamDisplayName = (teamId && showTeamName) ?
|
||||
observeTeam(database, teamId).pipe(
|
||||
switchMap((team) => of$(team?.displayName || '')),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
}
|
||||
) : of$('');
|
||||
|
||||
let membersCount = of$(0);
|
||||
if (channel.type === General.GM_CHANNEL) {
|
||||
membersCount = queryChannelMembers(database, channel.id).observeCount(false);
|
||||
}
|
||||
const membersCount = channel.type === General.GM_CHANNEL ?
|
||||
queryChannelMembers(database, channel.id).observeCount(false) :
|
||||
of$(0);
|
||||
|
||||
const isUnread = myChannel.pipe(
|
||||
switchMap((mc) => of$(mc?.isUnread)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const isUnread = shouldHighlightState ?
|
||||
myChannel.pipe(
|
||||
switchMap((mc) => of$(mc?.isUnread)),
|
||||
distinctUntilChanged(),
|
||||
) : of$(false);
|
||||
|
||||
const mentionsCount = myChannel.pipe(
|
||||
switchMap((mc) => of$(mc?.mentionsCount)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const hasMember = myChannel.pipe(
|
||||
switchMap((mc) => of$(Boolean(mc))),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const mentionsCount = shouldHighlightState ?
|
||||
myChannel.pipe(
|
||||
switchMap((mc) => of$(mc?.mentionsCount)),
|
||||
distinctUntilChanged(),
|
||||
) : of$(0);
|
||||
|
||||
const hasCall = observeChannelsWithCalls(serverUrl || '').pipe(
|
||||
switchMap((calls) => of$(Boolean(calls[channel.id]))),
|
||||
@@ -88,7 +90,7 @@ const enhance = withObservables(['channel', 'showTeamName'], ({
|
||||
);
|
||||
|
||||
return {
|
||||
channel: channel.observe(),
|
||||
channel: 'observe' in channel ? channel.observe() : of$(channel),
|
||||
currentUserId,
|
||||
hasDraft,
|
||||
isActive,
|
||||
@@ -97,7 +99,6 @@ const enhance = withObservables(['channel', 'showTeamName'], ({
|
||||
isUnread,
|
||||
mentionsCount,
|
||||
teamDisplayName,
|
||||
hasMember,
|
||||
hasCall,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,41 +1,35 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/custom_status/custom_status_emoji should match snapshot 1`] = `
|
||||
<View
|
||||
testID="test.custom_status.custom_status_emoji.calendar"
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
"color": "#000",
|
||||
"fontSize": 16,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
undefined,
|
||||
{
|
||||
"color": "#000",
|
||||
"fontSize": 16,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
📆
|
||||
</Text>
|
||||
</View>
|
||||
📆
|
||||
</Text>
|
||||
`;
|
||||
|
||||
exports[`components/custom_status/custom_status_emoji should match snapshot with props 1`] = `
|
||||
<View
|
||||
testID="test.custom_status.custom_status_emoji.calendar"
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
"color": "#000",
|
||||
"fontSize": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
undefined,
|
||||
{
|
||||
"color": "#000",
|
||||
"fontSize": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
📆
|
||||
</Text>
|
||||
</View>
|
||||
📆
|
||||
</Text>
|
||||
`;
|
||||
|
||||
@@ -26,7 +26,6 @@ describe('components/custom_status/custom_status_emoji', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus}
|
||||
testID='test'
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
@@ -38,7 +37,6 @@ describe('components/custom_status/custom_status_emoji', () => {
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus}
|
||||
emojiSize={34}
|
||||
testID='test'
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
|
||||
@@ -2,29 +2,26 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleProp, TextStyle, View} from 'react-native';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
|
||||
import type {EmojiCommonStyle} from '@typings/components/emoji';
|
||||
import type {StyleProp} from 'react-native';
|
||||
|
||||
interface ComponentProps {
|
||||
customStatus: UserCustomStatus;
|
||||
emojiSize?: number;
|
||||
style?: StyleProp<TextStyle>;
|
||||
testID?: string;
|
||||
style?: StyleProp<EmojiCommonStyle>;
|
||||
}
|
||||
|
||||
const CustomStatusEmoji = ({customStatus, emojiSize = 16, style, testID}: ComponentProps) => {
|
||||
const CustomStatusEmoji = ({customStatus, emojiSize = 16, style}: ComponentProps) => {
|
||||
if (customStatus.emoji) {
|
||||
return (
|
||||
<View
|
||||
style={style}
|
||||
testID={`${testID}.custom_status.custom_status_emoji.${customStatus.emoji}`}
|
||||
>
|
||||
<Emoji
|
||||
size={emojiSize}
|
||||
emojiName={customStatus.emoji}
|
||||
/>
|
||||
</View>
|
||||
<Emoji
|
||||
size={emojiSize}
|
||||
emojiName={customStatus.emoji}
|
||||
commonStyle={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,13 @@ const assetImages = new Map([['mattermost.png', require('@assets/images/emojis/m
|
||||
const Emoji = (props: EmojiProps) => {
|
||||
const {
|
||||
customEmojis,
|
||||
customEmojiStyle,
|
||||
imageStyle,
|
||||
displayTextOnly,
|
||||
emojiName,
|
||||
literal = '',
|
||||
testID,
|
||||
textStyle,
|
||||
commonStyle,
|
||||
} = props;
|
||||
const serverUrl = useServerUrl();
|
||||
let assetImage = '';
|
||||
@@ -73,7 +74,7 @@ const Emoji = (props: EmojiProps) => {
|
||||
if (displayTextOnly || (!imageUrl && !assetImage && !unicode)) {
|
||||
return (
|
||||
<Text
|
||||
style={textStyle}
|
||||
style={[commonStyle, textStyle]}
|
||||
testID={testID}
|
||||
>
|
||||
{literal}
|
||||
@@ -91,7 +92,7 @@ const Emoji = (props: EmojiProps) => {
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[textStyle, {fontSize: size, color: '#000'}]}
|
||||
style={[commonStyle, textStyle, {fontSize: size, color: '#000'}]}
|
||||
testID={testID}
|
||||
>
|
||||
{code}
|
||||
@@ -110,7 +111,7 @@ const Emoji = (props: EmojiProps) => {
|
||||
<FastImage
|
||||
key={key}
|
||||
source={image}
|
||||
style={[customEmojiStyle, {width, height}]}
|
||||
style={[commonStyle, imageStyle, {width, height}]}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
testID={testID}
|
||||
/>
|
||||
@@ -128,7 +129,7 @@ const Emoji = (props: EmojiProps) => {
|
||||
return (
|
||||
<FastImage
|
||||
key={key}
|
||||
style={[customEmojiStyle, {width, height}]}
|
||||
style={[commonStyle, imageStyle, {width, height}]}
|
||||
source={{uri: imageUrl}}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
testID={testID}
|
||||
|
||||
@@ -121,7 +121,6 @@ const HeaderDisplayName = ({
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus!}
|
||||
style={[style.customStatusEmoji]}
|
||||
testID='post_header'
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -5,8 +5,8 @@ import React, {useCallback, useMemo, useState} from 'react';
|
||||
import {View, Text, StyleSheet} from 'react-native';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import TouchableWithFeedback from '@app/components/touchable_with_feedback';
|
||||
import {useServerUrl} from '@app/context/server';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import {Platform, StyleProp, View, ViewStyle} from 'react-native';
|
||||
import {StyleProp, View, ViewStyle} from 'react-native';
|
||||
|
||||
import {fetchStatusInBatch} from '@actions/remote/user';
|
||||
import {useServerUrl} from '@context/server';
|
||||
@@ -15,11 +15,6 @@ import Status from './status';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {Source} from 'react-native-fast-image';
|
||||
|
||||
const STATUS_BUFFER = Platform.select({
|
||||
ios: 3,
|
||||
android: 2,
|
||||
});
|
||||
|
||||
type ProfilePictureProps = {
|
||||
author?: UserModel | UserProfile;
|
||||
forwardRef?: React.RefObject<any>;
|
||||
@@ -27,6 +22,7 @@ type ProfilePictureProps = {
|
||||
showStatus?: boolean;
|
||||
size: number;
|
||||
statusSize?: number;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
statusStyle?: StyleProp<ViewStyle>;
|
||||
testID?: string;
|
||||
source?: Source | string;
|
||||
@@ -38,7 +34,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 80,
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.48),
|
||||
@@ -67,6 +62,7 @@ const ProfilePicture = ({
|
||||
showStatus = true,
|
||||
size = 64,
|
||||
statusSize = 14,
|
||||
containerStyle,
|
||||
statusStyle,
|
||||
testID,
|
||||
source,
|
||||
@@ -77,7 +73,6 @@ const ProfilePicture = ({
|
||||
serverUrl = url || serverUrl;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const buffer = showStatus ? STATUS_BUFFER || 0 : 0;
|
||||
const isBot = author && (('isBot' in author) ? author.isBot : author.is_bot);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -86,26 +81,14 @@ const ProfilePicture = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const containerStyle = useMemo(() => {
|
||||
if (author) {
|
||||
return {
|
||||
width: size + (buffer - 1),
|
||||
height: size + (buffer - 1),
|
||||
borderRadius: (size + buffer) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...style.container,
|
||||
width: size + buffer,
|
||||
height: size + buffer,
|
||||
borderRadius: (size + buffer) / 2,
|
||||
};
|
||||
}, [author, size]);
|
||||
const viewStyle = useMemo(
|
||||
() => [style.container, {width: size, height: size}, containerStyle],
|
||||
[style, size, containerStyle],
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={containerStyle}
|
||||
style={viewStyle}
|
||||
testID={testID}
|
||||
>
|
||||
<Image
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {StyleProp, View, ViewStyle} from 'react-native';
|
||||
import {Platform, StyleProp, View, ViewStyle} from 'react-native';
|
||||
|
||||
import UserStatus from '@components/user_status';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
@@ -16,12 +16,17 @@ type Props = {
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const STATUS_BUFFER = Platform.select({
|
||||
ios: 3,
|
||||
default: 2,
|
||||
});
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
statusWrapper: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
bottom: -STATUS_BUFFER,
|
||||
right: -STATUS_BUFFER,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {Text, TouchableOpacity, View} from 'react-native';
|
||||
import {Text, TouchableOpacity, useWindowDimensions} from 'react-native';
|
||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {nonBreakingString} from '@utils/strings';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
@@ -35,11 +36,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
marginRight: 8,
|
||||
paddingHorizontal: 7,
|
||||
},
|
||||
extraContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
text: {
|
||||
marginLeft: 8,
|
||||
color: theme.centerChannelColor,
|
||||
@@ -61,6 +57,7 @@ export default function SelectedChip({
|
||||
}: SelectedChipProps) {
|
||||
const theme = useTheme();
|
||||
const style = getStyleFromTheme(theme);
|
||||
const dimensions = useWindowDimensions();
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
onRemove(id);
|
||||
@@ -73,16 +70,13 @@ export default function SelectedChip({
|
||||
style={style.container}
|
||||
testID={testID}
|
||||
>
|
||||
{extra && (
|
||||
<View style={style.extraContent}>
|
||||
{extra}
|
||||
</View>
|
||||
)}
|
||||
{extra}
|
||||
<Text
|
||||
style={style.text}
|
||||
style={[style.text, {maxWidth: dimensions.width * 0.70}]}
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.display_name`}
|
||||
>
|
||||
{text}
|
||||
{nonBreakingString(text)}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={style.remove}
|
||||
|
||||
@@ -8,6 +8,8 @@ import ProfilePicture from '@components/profile_picture';
|
||||
import SelectedChip from '@components/selected_chip';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type Props = {
|
||||
|
||||
/*
|
||||
@@ -18,7 +20,7 @@ type Props = {
|
||||
/*
|
||||
* The user that this component represents.
|
||||
*/
|
||||
user: UserProfile;
|
||||
user: UserProfile|UserModel;
|
||||
|
||||
/*
|
||||
* A handler function that will deselect a user when clicked on.
|
||||
|
||||
@@ -11,7 +11,6 @@ import {filterProfilesMatchingTerm} from '@utils/user';
|
||||
|
||||
type Props = {
|
||||
currentUserId: string;
|
||||
teammateNameDisplay: string;
|
||||
tutorialWatched: boolean;
|
||||
handleSelectProfile: (user: UserProfile) => void;
|
||||
term: string;
|
||||
@@ -24,7 +23,6 @@ type Props = {
|
||||
|
||||
export default function ServerUserList({
|
||||
currentUserId,
|
||||
teammateNameDisplay,
|
||||
tutorialWatched,
|
||||
handleSelectProfile,
|
||||
term,
|
||||
@@ -121,7 +119,6 @@ export default function ServerUserList({
|
||||
profiles={data}
|
||||
selectedIds={selectedIds}
|
||||
showNoResults={!loading && page.current !== -1}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
fetchMore={getProfiles}
|
||||
term={term}
|
||||
testID={testID}
|
||||
|
||||
@@ -25,9 +25,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
alignSelf: 'center',
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
borderRadius: 4,
|
||||
marginRight: 2,
|
||||
marginBottom: 1,
|
||||
marginLeft: 2,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
|
||||
@@ -38,70 +38,62 @@ exports[`Thread item in the channel list Threads Component should match snapshot
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"marginLeft": -18,
|
||||
"marginRight": -20,
|
||||
}
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
},
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
<Icon
|
||||
name="message-text-outline"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
"color": "rgba(255,255,255,0.5)",
|
||||
"fontSize": 24,
|
||||
"marginRight": 12,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
{
|
||||
"flex": 1,
|
||||
},
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
},
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="message-text-outline"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.5)",
|
||||
"fontSize": 24,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
{
|
||||
"flex": 1,
|
||||
},
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Threads
|
||||
</Text>
|
||||
</View>
|
||||
Threads
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`Thread item in the channel list Threads Component should match snapshot with isInfo 1`] = `
|
||||
exports[`Thread item in the channel list Threads Component should match snapshot with onCenterBg 1`] = `
|
||||
<View
|
||||
accessibilityState={
|
||||
{
|
||||
@@ -139,70 +131,61 @@ exports[`Thread item in the channel list Threads Component should match snapshot
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"marginLeft": -18,
|
||||
"marginRight": -20,
|
||||
}
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
},
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
<Icon
|
||||
name="message-text-outline"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
"color": "rgba(255,255,255,0.5)",
|
||||
"fontSize": 24,
|
||||
"marginRight": 12,
|
||||
},
|
||||
false,
|
||||
{
|
||||
"color": "rgba(63,67,80,0.72)",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
{
|
||||
"flex": 1,
|
||||
},
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
},
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"color": "#3f4350",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="message-text-outline"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.5)",
|
||||
"fontSize": 24,
|
||||
},
|
||||
false,
|
||||
{
|
||||
"color": "rgba(63,67,80,0.72)",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
{
|
||||
"flex": 1,
|
||||
},
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"color": "#3f4350",
|
||||
"paddingRight": 20,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
Threads
|
||||
</Text>
|
||||
</View>
|
||||
Threads
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
@@ -247,65 +230,57 @@ exports[`Thread item in the channel list Threads Component should match snapshot
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"marginLeft": -18,
|
||||
"marginRight": -20,
|
||||
}
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
},
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
<Icon
|
||||
name="message-text-outline"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
"color": "rgba(255,255,255,0.5)",
|
||||
"fontSize": 24,
|
||||
"marginRight": 12,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
{
|
||||
"flex": 1,
|
||||
},
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
},
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="message-text-outline"
|
||||
style={
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.5)",
|
||||
"fontSize": 24,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
[
|
||||
{
|
||||
"flex": 1,
|
||||
},
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Threads
|
||||
</Text>
|
||||
</View>
|
||||
Threads
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -36,11 +36,11 @@ describe('Thread item in the channel list', () => {
|
||||
expect(toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Threads Component should match snapshot with isInfo', () => {
|
||||
test('Threads Component should match snapshot with onCenterBg', () => {
|
||||
const {toJSON} = renderWithIntlAndTheme(
|
||||
<Threads
|
||||
{...baseProps}
|
||||
isInfo={true}
|
||||
onCenterBg={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ import {switchToGlobalThreads} from '@actions/local/thread';
|
||||
import Badge from '@components/badge';
|
||||
import {
|
||||
getStyleSheet as getChannelItemStyleSheet,
|
||||
ROW_HEIGHT,
|
||||
textStyle as channelItemTextStyle,
|
||||
} from '@components/channel_item/channel_item';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {HOME_PADDING} from '@constants/view';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
@@ -19,13 +21,10 @@ import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
baseContainer: {
|
||||
marginLeft: -18,
|
||||
marginRight: -20,
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.sidebarText, 0.5),
|
||||
fontSize: 24,
|
||||
marginRight: 12,
|
||||
},
|
||||
iconActive: {
|
||||
color: theme.sidebarText,
|
||||
@@ -41,16 +40,27 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
type Props = {
|
||||
currentChannelId: string;
|
||||
groupUnreadsSeparately: boolean;
|
||||
isInfo?: boolean;
|
||||
onCenterBg?: boolean;
|
||||
onlyUnreads: boolean;
|
||||
onPress?: () => void;
|
||||
shouldHighlighActive?: boolean;
|
||||
unreadsAndMentions: {
|
||||
unreads: boolean;
|
||||
mentions: number;
|
||||
};
|
||||
isOnHome?: boolean;
|
||||
};
|
||||
|
||||
const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, isInfo, onlyUnreads, onPress, unreadsAndMentions}: Props) => {
|
||||
const ThreadsButton = ({
|
||||
currentChannelId,
|
||||
groupUnreadsSeparately,
|
||||
onCenterBg,
|
||||
onlyUnreads,
|
||||
onPress,
|
||||
unreadsAndMentions,
|
||||
shouldHighlighActive = false,
|
||||
isOnHome = false,
|
||||
}: Props) => {
|
||||
const isTablet = useIsTablet();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
@@ -67,18 +77,23 @@ const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, isInfo, onlyUn
|
||||
}), [onPress, serverUrl]);
|
||||
|
||||
const {unreads, mentions} = unreadsAndMentions;
|
||||
const isActive = isTablet && !currentChannelId;
|
||||
const isActive = isTablet && shouldHighlighActive && !currentChannelId;
|
||||
|
||||
const [containerStyle, iconStyle, textStyle, badgeStyle] = useMemo(() => {
|
||||
const container = [
|
||||
styles.container,
|
||||
isOnHome && HOME_PADDING,
|
||||
isActive && styles.activeItem,
|
||||
isActive && isOnHome && {
|
||||
paddingLeft: HOME_PADDING.paddingLeft - styles.activeItem.borderLeftWidth,
|
||||
},
|
||||
{minHeight: ROW_HEIGHT},
|
||||
];
|
||||
|
||||
const icon = [
|
||||
customStyles.icon,
|
||||
(isActive || unreads) && customStyles.iconActive,
|
||||
isInfo && customStyles.iconInfo,
|
||||
onCenterBg && customStyles.iconInfo,
|
||||
];
|
||||
|
||||
const text = [
|
||||
@@ -87,16 +102,16 @@ const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, isInfo, onlyUn
|
||||
styles.text,
|
||||
unreads && styles.highlight,
|
||||
isActive && styles.textActive,
|
||||
isInfo && styles.textInfo,
|
||||
onCenterBg && styles.textOnCenterBg,
|
||||
];
|
||||
|
||||
const badge = [
|
||||
styles.badge,
|
||||
isInfo && styles.infoBadge,
|
||||
onCenterBg && styles.badgeOnCenterBg,
|
||||
];
|
||||
|
||||
return [container, icon, text, badge];
|
||||
}, [customStyles, isActive, isInfo, styles, unreads]);
|
||||
}, [customStyles, isActive, onCenterBg, styles, unreads, isOnHome]);
|
||||
|
||||
if (groupUnreadsSeparately && (onlyUnreads && !isActive && !unreads && !mentions)) {
|
||||
return null;
|
||||
@@ -107,23 +122,21 @@ const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, isInfo, onlyUn
|
||||
onPress={handlePress}
|
||||
testID='channel_list.threads.button'
|
||||
>
|
||||
<View style={customStyles.baseContainer}>
|
||||
<View style={containerStyle}>
|
||||
<CompassIcon
|
||||
name='message-text-outline'
|
||||
style={iconStyle}
|
||||
/>
|
||||
<FormattedText
|
||||
id='threads'
|
||||
defaultMessage='Threads'
|
||||
style={textStyle}
|
||||
/>
|
||||
<Badge
|
||||
value={mentions}
|
||||
style={badgeStyle}
|
||||
visible={mentions > 0}
|
||||
/>
|
||||
</View>
|
||||
<View style={containerStyle}>
|
||||
<CompassIcon
|
||||
name='message-text-outline'
|
||||
style={iconStyle}
|
||||
/>
|
||||
<FormattedText
|
||||
id='threads'
|
||||
defaultMessage='Threads'
|
||||
style={textStyle}
|
||||
/>
|
||||
<Badge
|
||||
value={mentions}
|
||||
style={badgeStyle}
|
||||
visible={mentions > 0}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {BottomSheetFlatList} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent, PanResponder, StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native';
|
||||
import {Keyboard, ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent, PanResponder} from 'react-native';
|
||||
import {FlatList} from 'react-native-gesture-handler';
|
||||
|
||||
import UserItem from '@components/user_item';
|
||||
@@ -23,40 +23,30 @@ type Props = {
|
||||
|
||||
type ItemProps = {
|
||||
channelId: string;
|
||||
containerStyle: StyleProp<ViewStyle>;
|
||||
location: string;
|
||||
user: UserModel;
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
paddingLeft: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const Item = ({channelId, containerStyle, location, user}: ItemProps) => {
|
||||
const Item = ({channelId, location, user}: ItemProps) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const openUserProfile = async () => {
|
||||
if (user) {
|
||||
await dismissBottomSheet(Screens.BOTTOM_SHEET);
|
||||
const screen = Screens.USER_PROFILE;
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const closeButtonId = 'close-user-profile';
|
||||
const props = {closeButtonId, location, userId: user.id, channelId};
|
||||
|
||||
Keyboard.dismiss();
|
||||
openAsBottomSheet({screen, title, theme, closeButtonId, props});
|
||||
}
|
||||
};
|
||||
const openUserProfile = useCallback(async (u: UserModel | UserProfile) => {
|
||||
await dismissBottomSheet(Screens.BOTTOM_SHEET);
|
||||
const screen = Screens.USER_PROFILE;
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const closeButtonId = 'close-user-profile';
|
||||
const props = {closeButtonId, location, userId: u.id, channelId};
|
||||
|
||||
Keyboard.dismiss();
|
||||
openAsBottomSheet({screen, title, theme, closeButtonId, props});
|
||||
}, [location, channelId, theme, intl]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={openUserProfile}>
|
||||
<UserItem
|
||||
user={user}
|
||||
containerStyle={containerStyle}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<UserItem
|
||||
user={user}
|
||||
onUserPress={openUserProfile}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -89,7 +79,6 @@ const UsersList = ({channelId, location, type = 'FlatList', users}: Props) => {
|
||||
channelId={channelId}
|
||||
location={location}
|
||||
user={item}
|
||||
containerStyle={style.container}
|
||||
/>
|
||||
), [channelId, location]);
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeConfigBooleanValue, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeCurrentUser, observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
|
||||
import UserItem from './user_item';
|
||||
|
||||
@@ -12,12 +15,17 @@ import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const isCustomStatusEnabled = observeConfigBooleanValue(database, 'EnableCustomUserStatuses');
|
||||
const showFullName = observeConfigBooleanValue(database, 'ShowFullName');
|
||||
const currentUserId = observeCurrentUserId(database);
|
||||
const locale = observeCurrentUser(database).pipe(
|
||||
switchMap((u) => of$(u?.locale)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const teammateNameDisplay = observeTeammateNameDisplay(database);
|
||||
return {
|
||||
isCustomStatusEnabled,
|
||||
showFullName,
|
||||
currentUserId,
|
||||
locale,
|
||||
teammateNameDisplay,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,104 +1,92 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {IntlShape, useIntl} from 'react-intl';
|
||||
import {StyleProp, Text, View, ViewStyle} from 'react-native';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import CompassIcon from '@components/compass_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 {nonBreakingString} from '@utils/strings';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {getUserCustomStatus, isBot, isCustomStatusExpired, isGuest, isShared} from '@utils/user';
|
||||
import {displayUsername, getUserCustomStatus, isBot, isCustomStatusExpired, isGuest, isShared} from '@utils/user';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type AtMentionItemProps = {
|
||||
user?: UserProfile | UserModel;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
user: UserProfile | UserModel;
|
||||
currentUserId: string;
|
||||
showFullName: boolean;
|
||||
testID?: string;
|
||||
isCustomStatusEnabled: boolean;
|
||||
pictureContainerStyle?: StyleProp<ViewStyle>;
|
||||
showBadges?: boolean;
|
||||
locale?: string;
|
||||
teammateNameDisplay: string;
|
||||
rightDecorator?: React.ReactNode;
|
||||
onUserPress?: (user: UserProfile | UserModel) => void;
|
||||
onUserLongPress?: (user: UserProfile | UserModel) => void;
|
||||
disabled?: boolean;
|
||||
viewRef?: React.LegacyRef<View>;
|
||||
padding?: number;
|
||||
}
|
||||
|
||||
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) => {
|
||||
const getThemedStyles = 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: {
|
||||
...typography('Body', 200),
|
||||
color: theme.centerChannelColor,
|
||||
paddingLeft: 4,
|
||||
flex: 0,
|
||||
flexShrink: 1,
|
||||
},
|
||||
rowUsername: {
|
||||
...typography('Body', 200),
|
||||
...typography('Body', 100),
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
fontSize: 15,
|
||||
fontFamily: 'OpenSans',
|
||||
},
|
||||
icon: {
|
||||
marginLeft: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const nonThemedStyles = StyleSheet.create({
|
||||
row: {
|
||||
height: 40,
|
||||
paddingVertical: 8,
|
||||
paddingTop: 4,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
icon: {
|
||||
marginLeft: 4,
|
||||
},
|
||||
profile: {
|
||||
marginRight: 12,
|
||||
},
|
||||
tag: {
|
||||
marginLeft: 6,
|
||||
},
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const UserItem = ({
|
||||
containerStyle,
|
||||
user,
|
||||
currentUserId,
|
||||
showFullName,
|
||||
testID,
|
||||
isCustomStatusEnabled,
|
||||
pictureContainerStyle,
|
||||
showBadges = false,
|
||||
locale,
|
||||
teammateNameDisplay,
|
||||
rightDecorator,
|
||||
onUserPress,
|
||||
onUserLongPress,
|
||||
disabled = false,
|
||||
viewRef,
|
||||
padding,
|
||||
}: AtMentionItemProps) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleFromTheme(theme);
|
||||
const style = getThemedStyles(theme);
|
||||
const intl = useIntl();
|
||||
|
||||
const bot = user ? isBot(user) : false;
|
||||
@@ -106,91 +94,109 @@ const UserItem = ({
|
||||
const shared = user ? isShared(user) : false;
|
||||
|
||||
const isCurrentUser = currentUserId === user?.id;
|
||||
const name = getName(user, showFullName, isCurrentUser, intl);
|
||||
const customStatus = getUserCustomStatus(user);
|
||||
const customStatusExpired = isCustomStatusExpired(user);
|
||||
|
||||
const userItemTestId = `${testID}.${user?.id}`;
|
||||
const deleteAt = 'deleteAt' in user ? user.deleteAt : user.delete_at;
|
||||
|
||||
let rowUsernameFlexShrink = 1;
|
||||
if (user) {
|
||||
for (const rowInfoElem of [bot, guest, Boolean(name.length), isCurrentUser]) {
|
||||
if (rowInfoElem) {
|
||||
rowUsernameFlexShrink++;
|
||||
}
|
||||
}
|
||||
let displayName = displayUsername(user, locale, teammateNameDisplay);
|
||||
const showTeammateDisplay = displayName !== user?.username;
|
||||
if (isCurrentUser) {
|
||||
displayName = intl.formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName});
|
||||
}
|
||||
|
||||
const usernameTextStyle = useMemo(() => {
|
||||
return [style.rowUsername, {flexShrink: rowUsernameFlexShrink}];
|
||||
}, [user, rowUsernameFlexShrink]);
|
||||
const userItemTestId = `${testID}.${user?.id}`;
|
||||
|
||||
const containerStyle = useMemo(() => {
|
||||
return [
|
||||
nonThemedStyles.row,
|
||||
{
|
||||
opacity: disabled ? 0.32 : 1,
|
||||
paddingHorizontal: padding || undefined,
|
||||
},
|
||||
];
|
||||
}, [disabled, padding]);
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
onUserPress?.(user);
|
||||
}, [user, onUserPress]);
|
||||
|
||||
const onLongPress = useCallback(() => {
|
||||
onUserLongPress?.(user);
|
||||
}, [user, onUserLongPress]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[style.row, containerStyle]}
|
||||
testID={userItemTestId}
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
disabled={!(onUserPress || onUserLongPress)}
|
||||
>
|
||||
<View style={[style.rowPicture, pictureContainerStyle]}>
|
||||
<View
|
||||
ref={viewRef}
|
||||
style={containerStyle}
|
||||
testID={userItemTestId}
|
||||
>
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={24}
|
||||
showStatus={false}
|
||||
testID={`${userItemTestId}.profile_picture`}
|
||||
containerStyle={nonThemedStyles.profile}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={[style.rowInfo, {maxWidth: shared ? '75%' : '85%'}]}
|
||||
>
|
||||
{bot && <BotTag testID={`${userItemTestId}.bot.tag`}/>}
|
||||
{guest && <GuestTag testID={`${userItemTestId}.guest.tag`}/>}
|
||||
{Boolean(name.length) &&
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
testID={`${userItemTestId}.display_name`}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
}
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage=' (you)'
|
||||
style={style.rowUsername}
|
||||
testID={`${userItemTestId}.current_user_indicator`}
|
||||
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
testID={`${userItemTestId}.display_name`}
|
||||
>
|
||||
{nonBreakingString(displayName)}
|
||||
{Boolean(showTeammateDisplay) && (
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
testID={`${userItemTestId}.username`}
|
||||
>
|
||||
{nonBreakingString(` @${user!.username}`)}
|
||||
</Text>
|
||||
)}
|
||||
{Boolean(deleteAt) && (
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
testID={`${userItemTestId}.deactivated`}
|
||||
>
|
||||
{nonBreakingString(` ${intl.formatMessage({id: 'mobile.user_list.deactivated', defaultMessage: 'Deactivated'})}`)}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
{showBadges && bot && (
|
||||
<BotTag
|
||||
testID={`${userItemTestId}.bot.tag`}
|
||||
style={nonThemedStyles.tag}
|
||||
/>
|
||||
}
|
||||
{Boolean(user) && (
|
||||
<Text
|
||||
style={usernameTextStyle}
|
||||
numberOfLines={1}
|
||||
testID={`${userItemTestId}.username`}
|
||||
>
|
||||
{` @${user!.username}`}
|
||||
</Text>
|
||||
)}
|
||||
{showBadges && guest && (
|
||||
<GuestTag
|
||||
testID={`${userItemTestId}.guest.tag`}
|
||||
style={nonThemedStyles.tag}
|
||||
/>
|
||||
)}
|
||||
{Boolean(isCustomStatusEnabled && !bot && customStatus?.emoji && !customStatusExpired) && (
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus!}
|
||||
style={nonThemedStyles.icon}
|
||||
/>
|
||||
)}
|
||||
{shared && (
|
||||
<CompassIcon
|
||||
name={'circle-multiple-outline'}
|
||||
size={16}
|
||||
color={theme.centerChannelColor}
|
||||
style={nonThemedStyles.icon}
|
||||
/>
|
||||
)}
|
||||
<View style={nonThemedStyles.flex}/>
|
||||
{Boolean(rightDecorator) && rightDecorator}
|
||||
</View>
|
||||
{Boolean(isCustomStatusEnabled && !bot && customStatus?.emoji && !customStatusExpired) && (
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus!}
|
||||
style={style.icon}
|
||||
testID={userItemTestId}
|
||||
/>
|
||||
)}
|
||||
{shared && (
|
||||
<ChannelIcon
|
||||
name={name}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
size={18}
|
||||
shared={true}
|
||||
type={General.DM_CHANNEL}
|
||||
style={style.icon}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -77,7 +77,6 @@ describe('components/channel_list_row', () => {
|
||||
profiles={[user]}
|
||||
testID='UserListRow'
|
||||
currentUserId={'1'}
|
||||
teammateNameDisplay={'johndoe'}
|
||||
handleSelectProfile={() => {
|
||||
// noop
|
||||
}}
|
||||
@@ -101,7 +100,6 @@ describe('components/channel_list_row', () => {
|
||||
profiles={[user]}
|
||||
testID='UserListRow'
|
||||
currentUserId={'1'}
|
||||
teammateNameDisplay={'johndoe'}
|
||||
handleSelectProfile={() => {
|
||||
// noop
|
||||
}}
|
||||
@@ -125,7 +123,6 @@ describe('components/channel_list_row', () => {
|
||||
profiles={[user, user2]}
|
||||
testID='UserListRow'
|
||||
currentUserId={'1'}
|
||||
teammateNameDisplay={'johndoe'}
|
||||
handleSelectProfile={() => {
|
||||
// noop
|
||||
}}
|
||||
@@ -149,7 +146,6 @@ describe('components/channel_list_row', () => {
|
||||
profiles={[user]}
|
||||
testID='UserListRow'
|
||||
currentUserId={'1'}
|
||||
teammateNameDisplay={'johndoe'}
|
||||
handleSelectProfile={() => {
|
||||
// noop
|
||||
}}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type UserProfileWithChannelAdmin = UserProfile & {scheme_admin?: boolean}
|
||||
type RenderItemType = ListRenderItemInfo<UserProfileWithChannelAdmin> & {section?: SectionListData<UserProfileWithChannelAdmin>}
|
||||
|
||||
@@ -147,8 +149,7 @@ type Props = {
|
||||
profiles: UserProfile[];
|
||||
channelMembers?: ChannelMembership[];
|
||||
currentUserId: string;
|
||||
teammateNameDisplay: string;
|
||||
handleSelectProfile: (user: UserProfile) => void;
|
||||
handleSelectProfile: (user: UserProfile | UserModel) => void;
|
||||
fetchMore?: () => void;
|
||||
loading: boolean;
|
||||
manageMode?: boolean;
|
||||
@@ -165,7 +166,6 @@ export default function UserList({
|
||||
channelMembers,
|
||||
selectedIds,
|
||||
currentUserId,
|
||||
teammateNameDisplay,
|
||||
handleSelectProfile,
|
||||
fetchMore,
|
||||
loading,
|
||||
@@ -198,21 +198,29 @@ export default function UserList({
|
||||
return createProfilesSections(intl, profiles, channelMembers);
|
||||
}, [channelMembers, loading, profiles, term]);
|
||||
|
||||
const openUserProfile = useCallback(async (profile: UserProfile) => {
|
||||
const {user} = await storeProfile(serverUrl, profile);
|
||||
if (user) {
|
||||
const screen = Screens.USER_PROFILE;
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const closeButtonId = 'close-user-profile';
|
||||
const props = {
|
||||
closeButtonId,
|
||||
userId: user.id,
|
||||
location: Screens.USER_PROFILE,
|
||||
};
|
||||
|
||||
Keyboard.dismiss();
|
||||
openAsBottomSheet({screen, title, theme, closeButtonId, props});
|
||||
const openUserProfile = useCallback(async (profile: UserProfile | UserModel) => {
|
||||
let user: UserModel;
|
||||
if ('create_at' in profile) {
|
||||
const res = await storeProfile(serverUrl, profile);
|
||||
if (!res.user) {
|
||||
return;
|
||||
}
|
||||
user = res.user;
|
||||
} else {
|
||||
user = profile;
|
||||
}
|
||||
|
||||
const screen = Screens.USER_PROFILE;
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const closeButtonId = 'close-user-profile';
|
||||
const props = {
|
||||
closeButtonId,
|
||||
userId: user.id,
|
||||
location: Screens.USER_PROFILE,
|
||||
};
|
||||
|
||||
Keyboard.dismiss();
|
||||
openAsBottomSheet({screen, title, theme, closeButtonId, props});
|
||||
}, []);
|
||||
|
||||
const renderItem = useCallback(({item, index, section}: RenderItemType) => {
|
||||
@@ -237,12 +245,11 @@ export default function UserList({
|
||||
selected={selected}
|
||||
showManageMode={showManageMode}
|
||||
testID='create_direct_message.user_list.user_item'
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
tutorialWatched={tutorialWatched}
|
||||
user={item}
|
||||
/>
|
||||
);
|
||||
}, [selectedIds, handleSelectProfile, showManageMode, manageMode, teammateNameDisplay, tutorialWatched]);
|
||||
}, [selectedIds, handleSelectProfile, showManageMode, manageMode, tutorialWatched]);
|
||||
|
||||
const renderLoading = useCallback(() => {
|
||||
if (!loading) {
|
||||
|
||||
@@ -6,24 +6,22 @@ import {useIntl} from 'react-intl';
|
||||
import {
|
||||
InteractionManager,
|
||||
Platform,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import {storeProfileLongPressTutorial} from '@actions/app/global';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
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 TutorialHighlight from '@components/tutorial_highlight';
|
||||
import TutorialLongPress from '@components/tutorial_highlight/long_press';
|
||||
import UserItem from '@components/user_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {displayUsername, isGuest} from '@utils/user';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type Props = {
|
||||
highlight?: boolean;
|
||||
@@ -31,13 +29,12 @@ type Props = {
|
||||
isMyUser: boolean;
|
||||
isChannelAdmin: boolean;
|
||||
manageMode: boolean;
|
||||
onLongPress: (user: UserProfile) => void;
|
||||
onPress?: (user: UserProfile) => void;
|
||||
onLongPress: (user: UserProfile | UserModel) => void;
|
||||
onPress?: (user: UserProfile | UserModel) => void;
|
||||
selectable: boolean;
|
||||
disabled?: boolean;
|
||||
selected: boolean;
|
||||
showManageMode: boolean;
|
||||
teammateNameDisplay: string;
|
||||
testID: string;
|
||||
tutorialWatched?: boolean;
|
||||
user: UserProfile;
|
||||
@@ -45,54 +42,16 @@ type Props = {
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 20,
|
||||
height: 58,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
profileContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
textContainer: {
|
||||
paddingHorizontal: 10,
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
},
|
||||
username: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 75, 'Regular'),
|
||||
},
|
||||
displayName: {
|
||||
height: 24,
|
||||
color: theme.centerChannelColor,
|
||||
maxWidth: '80%',
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
indicatorContainer: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
deactivated: {
|
||||
marginTop: 2,
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
},
|
||||
sharedUserIcon: {
|
||||
alignSelf: 'center',
|
||||
opacity: 0.75,
|
||||
},
|
||||
selector: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 12,
|
||||
},
|
||||
selectorManage: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 12,
|
||||
},
|
||||
manageText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
@@ -107,7 +66,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
};
|
||||
});
|
||||
|
||||
const DISABLED_OPACITY = 0.32;
|
||||
const DEFAULT_ICON_OPACITY = 0.32;
|
||||
|
||||
function UserListRow({
|
||||
@@ -122,7 +80,6 @@ function UserListRow({
|
||||
disabled,
|
||||
selected,
|
||||
showManageMode = false,
|
||||
teammateNameDisplay,
|
||||
testID,
|
||||
tutorialWatched = false,
|
||||
user,
|
||||
@@ -133,8 +90,7 @@ function UserListRow({
|
||||
const [itemBounds, setItemBounds] = useState<TutorialItemBounds>({startX: 0, startY: 0, endX: 0, endY: 0});
|
||||
const viewRef = useRef<View>(null);
|
||||
const style = getStyleFromTheme(theme);
|
||||
const {formatMessage, locale} = useIntl();
|
||||
const {username} = user;
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const startTutorial = () => {
|
||||
viewRef.current?.measureInWindow((x, y, w, h) => {
|
||||
@@ -167,16 +123,12 @@ function UserListRow({
|
||||
}
|
||||
}, [highlight, tutorialWatched, isTablet]);
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
const handlePress = useCallback((u: UserModel | UserProfile) => {
|
||||
if (isMyUser && manageMode) {
|
||||
return;
|
||||
}
|
||||
onPress?.(user);
|
||||
}, [onPress, isMyUser, manageMode, user]);
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
onLongPress?.(user);
|
||||
}, [onLongPress, user]);
|
||||
onPress?.(u);
|
||||
}, [onPress, isMyUser, manageMode]);
|
||||
|
||||
const manageModeIcon = useMemo(() => {
|
||||
if (!showManageMode || isMyUser) {
|
||||
@@ -208,12 +160,11 @@ function UserListRow({
|
||||
}, []);
|
||||
|
||||
const icon = useMemo(() => {
|
||||
if (!selectable) {
|
||||
if (!selectable && !selected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const iconOpacity = DEFAULT_ICON_OPACITY * (disabled ? DISABLED_OPACITY : 1);
|
||||
const color = selected ? theme.buttonBg : changeOpacity(theme.centerChannelColor, iconOpacity);
|
||||
const color = selected ? theme.buttonBg : changeOpacity(theme.centerChannelColor, DEFAULT_ICON_OPACITY);
|
||||
return (
|
||||
<View style={style.selector}>
|
||||
<CompassIcon
|
||||
@@ -225,85 +176,21 @@ function UserListRow({
|
||||
);
|
||||
}, [selectable, disabled, selected, theme]);
|
||||
|
||||
let usernameDisplay = `@${username}`;
|
||||
if (isMyUser) {
|
||||
usernameDisplay = formatMessage({
|
||||
id: 'mobile.create_direct_message.you',
|
||||
defaultMessage: '@{username} - you',
|
||||
}, {username});
|
||||
}
|
||||
|
||||
const teammateDisplay = displayUsername(user, locale, teammateNameDisplay);
|
||||
const showTeammateDisplay = teammateDisplay !== username;
|
||||
|
||||
const userItemTestID = `${testID}.${id}`;
|
||||
const opacity = selectable || selected || !disabled ? 1 : DISABLED_OPACITY;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableWithFeedback
|
||||
onLongPress={handleLongPress}
|
||||
onPress={handlePress}
|
||||
underlayColor={changeOpacity(theme.centerChannelColor, 0.16)}
|
||||
>
|
||||
<View
|
||||
ref={viewRef}
|
||||
style={style.container}
|
||||
testID={userItemTestID}
|
||||
>
|
||||
<View style={[style.profileContainer, {opacity}]}>
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={40}
|
||||
iconSize={24}
|
||||
testID={`${userItemTestID}.profile_picture`}
|
||||
/>
|
||||
</View>
|
||||
<View style={[style.textContainer, {opacity}]}>
|
||||
<View style={style.indicatorContainer}>
|
||||
<Text
|
||||
style={style.displayName}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${userItemTestID}.display_name`}
|
||||
>
|
||||
{teammateDisplay}
|
||||
</Text>
|
||||
<BotTag
|
||||
show={Boolean(user.is_bot)}
|
||||
testID={`${userItemTestID}.bot.tag`}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest(user.roles)}
|
||||
testID={`${userItemTestID}.guest.tag`}
|
||||
/>
|
||||
</View>
|
||||
{showTeammateDisplay &&
|
||||
<View>
|
||||
<Text
|
||||
style={style.username}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${userItemTestID}.team_display_name`}
|
||||
>
|
||||
{usernameDisplay}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
{user.delete_at > 0 &&
|
||||
<View>
|
||||
<Text
|
||||
style={style.deactivated}
|
||||
testID={`${userItemTestID}.deactivated`}
|
||||
>
|
||||
{formatMessage({id: 'mobile.user_list.deactivated', defaultMessage: 'Deactivated'})}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
{manageMode ? manageModeIcon : icon}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
<UserItem
|
||||
user={user}
|
||||
onUserLongPress={onLongPress}
|
||||
onUserPress={handlePress}
|
||||
showBadges={true}
|
||||
testID={userItemTestID}
|
||||
rightDecorator={manageMode ? manageModeIcon : icon}
|
||||
disabled={!(selectable || selected || !disabled)}
|
||||
viewRef={viewRef}
|
||||
padding={20}
|
||||
/>
|
||||
{showTutorial &&
|
||||
<TutorialHighlight
|
||||
itemBounds={itemBounds}
|
||||
|
||||
@@ -27,6 +27,11 @@ export const CALL_ERROR_BAR_HEIGHT = 62;
|
||||
|
||||
export const ANNOUNCEMENT_BAR_HEIGHT = 40;
|
||||
|
||||
export const HOME_PADDING = {
|
||||
paddingLeft: 18,
|
||||
paddingRight: 20,
|
||||
};
|
||||
|
||||
export default {
|
||||
BOTTOM_TAB_HEIGHT,
|
||||
BOTTOM_TAB_ICON_SIZE,
|
||||
|
||||
@@ -206,7 +206,6 @@ const ChannelHeader = ({
|
||||
customStatus={customStatus}
|
||||
emojiSize={13}
|
||||
style={styles.customStatusEmoji}
|
||||
testID='channel_header'
|
||||
/>
|
||||
}
|
||||
<View style={styles.customStatusText}>
|
||||
|
||||
@@ -273,7 +273,6 @@ export default function ChannelAddMembers({
|
||||
currentUserId={currentUserId}
|
||||
handleSelectProfile={handleSelectProfile}
|
||||
selectedIds={selectedIds}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
term={term}
|
||||
testID={`${TEST_ID}.user_list`}
|
||||
tutorialWatched={tutorialWatched}
|
||||
|
||||
@@ -5,10 +5,10 @@ import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import {getHeaderOptions} from '@app/screens/channel_add_members/channel_add_members';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {getHeaderOptions} from '@screens/channel_add_members/channel_add_members';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ import {View} from 'react-native';
|
||||
import Animated, {FlipOutXUp} from 'react-native-reanimated';
|
||||
|
||||
import {toggleMuteChannel} from '@actions/remote/channel';
|
||||
import Button from '@app/components/button';
|
||||
import CompassIcon from '@app/components/compass_icon';
|
||||
import FormattedText from '@app/components/formatted_text';
|
||||
import {useServerUrl} from '@app/context/server';
|
||||
import {useTheme} from '@app/context/theme';
|
||||
import {preventDoubleTap} from '@app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme';
|
||||
import Button from '@components/button';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -320,7 +320,6 @@ export default function CreateDirectMessage({
|
||||
currentUserId={currentUserId}
|
||||
handleSelectProfile={handleSelectProfile}
|
||||
selectedIds={selectedIds}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
term={term}
|
||||
testID='create_direct_message.user_list'
|
||||
tutorialWatched={tutorialWatched}
|
||||
|
||||
@@ -14,14 +14,12 @@ import ChannelItem from '@components/channel_item';
|
||||
import Loading from '@components/loading';
|
||||
import NoResultsWithTerm from '@components/no_results_with_term';
|
||||
import ThreadsButton from '@components/threads_button';
|
||||
import UserItem from '@components/user_item';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {sortChannelsByDisplayName} from '@utils/channel';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import RemoteChannelItem from './remote_channel_item';
|
||||
import UserItem from './user_item';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
@@ -144,8 +142,9 @@ const FilteredList = ({
|
||||
onLoading(false);
|
||||
};
|
||||
|
||||
const onJoinChannel = useCallback(async (channelId: string, displayName: string) => {
|
||||
const {error} = await joinChannelIfNeeded(serverUrl, channelId);
|
||||
const onJoinChannel = useCallback(async (c: Channel | ChannelModel) => {
|
||||
const {error} = await joinChannelIfNeeded(serverUrl, c.id);
|
||||
const displayName = 'display_name' in c ? c.display_name : c.displayName;
|
||||
if (error) {
|
||||
Alert.alert(
|
||||
'',
|
||||
@@ -158,11 +157,12 @@ const FilteredList = ({
|
||||
}
|
||||
|
||||
await close();
|
||||
switchToChannelById(serverUrl, channelId, undefined, true);
|
||||
switchToChannelById(serverUrl, c.id, undefined, true);
|
||||
}, [serverUrl, close, locale]);
|
||||
|
||||
const onOpenDirectMessage = useCallback(async (teammateId: string, displayName: string) => {
|
||||
const {data, error} = await makeDirectChannel(serverUrl, teammateId, displayName, false);
|
||||
const onOpenDirectMessage = useCallback(async (u: UserProfile | UserModel) => {
|
||||
const displayName = displayUsername(u, locale, teammateDisplayNameSetting);
|
||||
const {data, error} = await makeDirectChannel(serverUrl, u.id, displayName, false);
|
||||
if (error || !data) {
|
||||
Alert.alert(
|
||||
'',
|
||||
@@ -176,11 +176,11 @@ const FilteredList = ({
|
||||
|
||||
await close();
|
||||
switchToChannelById(serverUrl, data.id);
|
||||
}, [serverUrl, close, locale]);
|
||||
}, [serverUrl, close, locale, teammateDisplayNameSetting]);
|
||||
|
||||
const onSwitchToChannel = useCallback(async (channelId: string) => {
|
||||
const onSwitchToChannel = useCallback(async (c: Channel | ChannelModel) => {
|
||||
await close();
|
||||
switchToChannelById(serverUrl, channelId);
|
||||
switchToChannelById(serverUrl, c.id);
|
||||
}, [serverUrl, close]);
|
||||
|
||||
const onSwitchToThreads = useCallback(async () => {
|
||||
@@ -214,7 +214,7 @@ const FilteredList = ({
|
||||
if (item === 'thread') {
|
||||
return (
|
||||
<ThreadsButton
|
||||
isInfo={true}
|
||||
onCenterBg={true}
|
||||
onPress={onSwitchToThreads}
|
||||
/>
|
||||
);
|
||||
@@ -223,27 +223,30 @@ const FilteredList = ({
|
||||
return (
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
isInfo={true}
|
||||
isOnCenterBg={true}
|
||||
onPress={onSwitchToChannel}
|
||||
showTeamName={showTeamName}
|
||||
shouldHighlightState={true}
|
||||
testID='find_channels.filtered_list.channel_item'
|
||||
/>
|
||||
);
|
||||
} else if ('username' in item) {
|
||||
return (
|
||||
<UserItem
|
||||
onPress={onOpenDirectMessage}
|
||||
onUserPress={onOpenDirectMessage}
|
||||
user={item}
|
||||
testID='find_channels.filtered_list.user_item'
|
||||
showBadges={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RemoteChannelItem
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
onPress={onJoinChannel}
|
||||
showTeamName={showTeamName}
|
||||
shouldHighlightState={true}
|
||||
testID='find_channels.filtered_list.remote_channel_item'
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import React from 'react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeTeam} from '@queries/servers/team';
|
||||
|
||||
import RemoteChannelItem from './remote_channel_item';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
type EnhanceProps = WithDatabaseArgs & {
|
||||
channel: Channel;
|
||||
showTeamName?: boolean;
|
||||
}
|
||||
|
||||
const enhance = withObservables(['channel', 'showTeamName'], ({channel, database, showTeamName}: EnhanceProps) => {
|
||||
let teamDisplayName = of$('');
|
||||
if (channel.team_id && showTeamName) {
|
||||
teamDisplayName = observeTeam(database, channel.team_id).pipe(
|
||||
switchMap((team) => of$(team?.displayName || '')),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
teamDisplayName,
|
||||
};
|
||||
});
|
||||
|
||||
export default React.memo(withDatabase(enhance(RemoteChannelItem)));
|
||||
@@ -1,123 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {Text, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
onPress: (channelId: string, displayName: string) => void;
|
||||
channel: Channel;
|
||||
teamDisplayName?: string;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 0,
|
||||
minHeight: 44,
|
||||
alignItems: 'center',
|
||||
marginVertical: 2,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
text: {
|
||||
marginTop: -1,
|
||||
color: theme.centerChannelColor,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
teamName: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
paddingLeft: 12,
|
||||
marginTop: 4,
|
||||
...typography('Body', 75),
|
||||
},
|
||||
teamNameTablet: {
|
||||
marginLeft: -12,
|
||||
paddingLeft: 0,
|
||||
marginTop: 0,
|
||||
paddingBottom: 0,
|
||||
top: 5,
|
||||
},
|
||||
}));
|
||||
|
||||
const RemoteChannelItem = ({onPress, channel, teamDisplayName, testID}: Props) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const styles = getStyleSheet(theme);
|
||||
const height = (teamDisplayName && !isTablet) ? 58 : 44;
|
||||
|
||||
const handleOnPress = useCallback(() => {
|
||||
onPress(channel.id, channel.display_name);
|
||||
}, [channel]);
|
||||
|
||||
const containerStyle = useMemo(() => [
|
||||
styles.container,
|
||||
{minHeight: height},
|
||||
],
|
||||
[height, styles]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={handleOnPress}>
|
||||
<>
|
||||
<View
|
||||
style={containerStyle}
|
||||
testID={`${testID}.${channel.name}`}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<ChannelIcon
|
||||
isInfo={true}
|
||||
isArchived={channel.delete_at > 0}
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
size={24}
|
||||
type={channel.type}
|
||||
/>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={styles.text}
|
||||
testID={`${testID}.${channel.name}.display_name`}
|
||||
>
|
||||
{channel.display_name}
|
||||
</Text>
|
||||
{Boolean(teamDisplayName) && !isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.${channel.name}.team_display_name`}
|
||||
style={styles.teamName}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
{Boolean(teamDisplayName) && isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.${channel.name}.team_display_name`}
|
||||
style={[styles.teamName, styles.teamNameTablet]}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default RemoteChannelItem;
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import React from 'react';
|
||||
|
||||
import {observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
|
||||
import UserItem from './user_item';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type EnhanceProps = WithDatabaseArgs & {
|
||||
user: UserModel;
|
||||
}
|
||||
|
||||
const enhance = withObservables(['user'], ({database, user}: EnhanceProps) => ({
|
||||
currentUserId: observeCurrentUserId(database),
|
||||
teammateDisplayNameSetting: observeTeammateNameDisplay(database),
|
||||
user: user.observe(),
|
||||
}));
|
||||
|
||||
export default React.memo(withDatabase(enhance(UserItem)));
|
||||
@@ -1,99 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Text, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import CustomStatus from '@components/channel_item/custom_status';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type Props = {
|
||||
currentUserId: string;
|
||||
onPress: (channelId: string, displayName: string) => void;
|
||||
teammateDisplayNameSetting?: string;
|
||||
testID?: string;
|
||||
user: UserModel;
|
||||
}
|
||||
|
||||
export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 0,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
marginVertical: 2,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
text: {
|
||||
marginTop: -1,
|
||||
color: theme.centerChannelColor,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
avatar: {marginLeft: 4},
|
||||
status: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderWidth: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
const UserItem = ({currentUserId, onPress, teammateDisplayNameSetting, testID, user}: Props) => {
|
||||
const {formatMessage, locale} = useIntl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const isOwnDirectMessage = currentUserId === user.id;
|
||||
const displayName = displayUsername(user, locale, teammateDisplayNameSetting);
|
||||
const userItemTestId = `${testID}.${user.id}`;
|
||||
|
||||
const handleOnPress = useCallback(() => {
|
||||
onPress(user.id, displayName);
|
||||
}, [user.id, displayName, onPress]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={handleOnPress}>
|
||||
<>
|
||||
<View
|
||||
style={styles.container}
|
||||
testID={userItemTestId}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<View style={styles.avatar}>
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={24}
|
||||
showStatus={true}
|
||||
statusSize={12}
|
||||
statusStyle={styles.status}
|
||||
testID={`${userItemTestId}.profile_picture`}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={styles.text}
|
||||
testID={`${userItemTestId}.display_name`}
|
||||
>
|
||||
{isOwnDirectMessage ? formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName}) : displayName}
|
||||
</Text>
|
||||
</View>
|
||||
<CustomStatus userId={user.id}/>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserItem;
|
||||
@@ -52,9 +52,9 @@ const UnfilteredList = ({close, keyboardHeight, recentChannels, showTeamName, te
|
||||
const [sections, setSections] = useState(buildSections(recentChannels));
|
||||
const sectionListStyle = useMemo(() => ({paddingBottom: keyboardHeight}), [keyboardHeight]);
|
||||
|
||||
const onPress = useCallback(async (channelId: string) => {
|
||||
const onPress = useCallback(async (c: Channel | ChannelModel) => {
|
||||
await close();
|
||||
switchToChannelById(serverUrl, channelId);
|
||||
switchToChannelById(serverUrl, c.id);
|
||||
}, [serverUrl, close]);
|
||||
|
||||
const renderSectionHeader = useCallback(({section}: SectionListRenderItemInfo<ChannelModel>) => (
|
||||
@@ -65,9 +65,10 @@ const UnfilteredList = ({close, keyboardHeight, recentChannels, showTeamName, te
|
||||
return (
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
isInfo={true}
|
||||
onPress={onPress}
|
||||
isOnCenterBg={true}
|
||||
showTeamName={showTeamName}
|
||||
shouldHighlightState={true}
|
||||
testID={`${testID}.channel_item`}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,10 +7,10 @@ import {Text, TouchableHighlight, View} from 'react-native';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import {fetchAndSwitchToThread} from '@actions/remote/thread';
|
||||
import TouchableWithFeedback from '@app/components/touchable_with_feedback';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import FriendlyDate from '@components/friendly_date';
|
||||
import RemoveMarkdown from '@components/remove_markdown';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {Screens} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
||||
@@ -15,8 +15,6 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
"backgroundColor": "#1e325c",
|
||||
"flex": 1,
|
||||
"maxWidth": 750,
|
||||
"paddingLeft": 18,
|
||||
"paddingRight": 20,
|
||||
"paddingTop": 10,
|
||||
}
|
||||
}
|
||||
@@ -33,6 +31,8 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
style={
|
||||
{
|
||||
"marginLeft": 0,
|
||||
"paddingLeft": 18,
|
||||
"paddingRight": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -350,8 +350,6 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
"backgroundColor": "#1e325c",
|
||||
"flex": 1,
|
||||
"maxWidth": 750,
|
||||
"paddingLeft": 18,
|
||||
"paddingRight": 20,
|
||||
"paddingTop": 10,
|
||||
}
|
||||
}
|
||||
@@ -368,6 +366,8 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
style={
|
||||
{
|
||||
"marginLeft": 0,
|
||||
"paddingLeft": 18,
|
||||
"paddingRight": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -467,6 +467,8 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
style={
|
||||
{
|
||||
"flexDirection": "row",
|
||||
"paddingLeft": 18,
|
||||
"paddingRight": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 're
|
||||
|
||||
import {fetchDirectChannelsInfo} from '@actions/remote/channel';
|
||||
import ChannelItem from '@components/channel_item';
|
||||
import {ROW_HEIGHT as CHANNEL_ROW_HEIGHT} from '@components/channel_item/channel_item';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
|
||||
@@ -16,7 +17,7 @@ import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
type Props = {
|
||||
sortedChannels: ChannelModel[];
|
||||
category: CategoryModel;
|
||||
onChannelSwitch: (channelId: string) => void;
|
||||
onChannelSwitch: (channel: Channel | ChannelModel) => void;
|
||||
unreadIds: Set<string>;
|
||||
unreadsOnTop: boolean;
|
||||
};
|
||||
@@ -46,6 +47,9 @@ const CategoryBody = ({sortedChannels, unreadIds, unreadsOnTop, category, onChan
|
||||
onPress={onChannelSwitch}
|
||||
key={item.id}
|
||||
testID={`channel_list.category.${category.displayName.replace(/ /g, '_').toLocaleLowerCase()}.channel_item`}
|
||||
shouldHighlightActive={true}
|
||||
shouldHighlightState={true}
|
||||
isOnHome={true}
|
||||
/>
|
||||
);
|
||||
}, [onChannelSwitch]);
|
||||
@@ -62,8 +66,8 @@ const CategoryBody = ({sortedChannels, unreadIds, unreadsOnTop, category, onChan
|
||||
}
|
||||
}, [directChannels.length]);
|
||||
|
||||
const height = ids.length ? ids.length * 40 : 0;
|
||||
const unreadHeight = unreadChannels.length ? unreadChannels.length * 40 : 0;
|
||||
const height = ids.length ? ids.length * CHANNEL_ROW_HEIGHT : 0;
|
||||
const unreadHeight = unreadChannels.length ? unreadChannels.length * CHANNEL_ROW_HEIGHT : 0;
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
const opacity = unreadHeight > 0 ? 1 : 0;
|
||||
@@ -74,7 +78,7 @@ const CategoryBody = ({sortedChannels, unreadIds, unreadsOnTop, category, onChan
|
||||
};
|
||||
}, [height, unreadHeight]);
|
||||
|
||||
const listHeight = useMemo(() => ({
|
||||
const listStyle = useMemo(() => ({
|
||||
height: category.collapsed ? unreadHeight : height,
|
||||
}), [category.collapsed, height, unreadHeight]);
|
||||
|
||||
@@ -87,7 +91,7 @@ const CategoryBody = ({sortedChannels, unreadIds, unreadsOnTop, category, onChan
|
||||
|
||||
// @ts-expect-error strictMode not exposed on the types
|
||||
strictMode={true}
|
||||
style={listHeight}
|
||||
style={listStyle}
|
||||
/>
|
||||
</Animated.View>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import CategoryHeader from './header';
|
||||
import UnreadCategories from './unreads';
|
||||
|
||||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
type Props = {
|
||||
categories: CategoryModel[];
|
||||
@@ -27,8 +28,6 @@ type Props = {
|
||||
const styles = StyleSheet.create({
|
||||
mainList: {
|
||||
flex: 1,
|
||||
marginLeft: -18,
|
||||
marginRight: -20,
|
||||
},
|
||||
loadingView: {
|
||||
alignItems: 'center',
|
||||
@@ -39,7 +38,11 @@ const styles = StyleSheet.create({
|
||||
|
||||
const extractKey = (item: CategoryModel | 'UNREADS') => (item === 'UNREADS' ? 'UNREADS' : item.id);
|
||||
|
||||
const Categories = ({categories, onlyUnreads, unreadsOnTop}: Props) => {
|
||||
const Categories = ({
|
||||
categories,
|
||||
onlyUnreads,
|
||||
unreadsOnTop,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const listRef = useRef<FlatList>(null);
|
||||
const serverUrl = useServerUrl();
|
||||
@@ -60,8 +63,8 @@ const Categories = ({categories, onlyUnreads, unreadsOnTop}: Props) => {
|
||||
|
||||
const [initiaLoad, setInitialLoad] = useState(!categoriesToShow.length);
|
||||
|
||||
const onChannelSwitch = useCallback(async (channelId: string) => {
|
||||
switchToChannelById(serverUrl, channelId);
|
||||
const onChannelSwitch = useCallback(async (c: Channel | ChannelModel) => {
|
||||
switchToChannelById(serverUrl, c.id);
|
||||
}, [serverUrl]);
|
||||
|
||||
const renderCategory = useCallback((data: {item: CategoryModel | 'UNREADS'}) => {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {FlatList, Text} from 'react-native';
|
||||
|
||||
import ChannelItem from '@components/channel_item';
|
||||
import {HOME_PADDING} from '@constants/view';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
@@ -25,14 +26,14 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
color: changeOpacity(theme.sidebarText, 0.64),
|
||||
...typography('Heading', 75),
|
||||
textTransform: 'uppercase',
|
||||
paddingLeft: 18,
|
||||
paddingVertical: 8,
|
||||
marginTop: 12,
|
||||
...HOME_PADDING,
|
||||
},
|
||||
}));
|
||||
|
||||
type UnreadCategoriesProps = {
|
||||
onChannelSwitch: (channelId: string) => void;
|
||||
onChannelSwitch: (channel: Channel | ChannelModel) => void;
|
||||
onlyUnreads: boolean;
|
||||
unreadChannels: ChannelModel[];
|
||||
unreadThreads: {unreads: boolean; mentions: number};
|
||||
@@ -52,11 +53,20 @@ const UnreadCategories = ({onChannelSwitch, onlyUnreads, unreadChannels, unreadT
|
||||
channel={item}
|
||||
onPress={onChannelSwitch}
|
||||
testID='channel_list.category.unreads.channel_item'
|
||||
shouldHighlightActive={true}
|
||||
shouldHighlightState={true}
|
||||
isOnHome={true}
|
||||
/>
|
||||
);
|
||||
}, [onChannelSwitch]);
|
||||
|
||||
const showEmptyState = onlyUnreads && !unreadChannels.length;
|
||||
const containerStyle = useMemo(() => {
|
||||
return [
|
||||
showEmptyState && !isTablet && styles.empty,
|
||||
];
|
||||
}, [styles, showEmptyState, isTablet]);
|
||||
|
||||
const showTitle = !onlyUnreads || (onlyUnreads && !showEmptyState);
|
||||
const EmptyState = showEmptyState && !isTablet ? (
|
||||
<Empty onlyUnreads={onlyUnreads}/>
|
||||
@@ -76,7 +86,7 @@ const UnreadCategories = ({onChannelSwitch, onlyUnreads, unreadChannels, unreadT
|
||||
</Text>
|
||||
}
|
||||
<FlatList
|
||||
contentContainerStyle={showEmptyState && !isTablet && styles.empty}
|
||||
contentContainerStyle={containerStyle}
|
||||
data={unreadChannels}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={extractKey}
|
||||
|
||||
@@ -13,6 +13,8 @@ exports[`components/channel_list/header Channel List Header Component should mat
|
||||
style={
|
||||
{
|
||||
"marginLeft": 0,
|
||||
"paddingLeft": 18,
|
||||
"paddingRight": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,7 @@ import CompassIcon from '@components/compass_icon';
|
||||
import {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {PUSH_PROXY_STATUS_NOT_AVAILABLE, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
|
||||
import {HOME_PADDING} from '@constants/view';
|
||||
import {useServerDisplayName, useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
@@ -266,7 +267,7 @@ const ChannelListHeader = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View style={animatedStyle}>
|
||||
<Animated.View style={[animatedStyle, HOME_PADDING]}>
|
||||
{header}
|
||||
</Animated.View>
|
||||
);
|
||||
|
||||
@@ -20,8 +20,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.sidebarBg,
|
||||
paddingLeft: 18,
|
||||
paddingRight: 20,
|
||||
paddingTop: 10,
|
||||
},
|
||||
}));
|
||||
@@ -68,7 +66,12 @@ const CategoriesList = ({hasChannels, iconPad, isCRTEnabled, moreThanOneTeam}: C
|
||||
return (
|
||||
<>
|
||||
<SubHeader/>
|
||||
{isCRTEnabled && <ThreadsButton/>}
|
||||
{isCRTEnabled &&
|
||||
<ThreadsButton
|
||||
isOnHome={true}
|
||||
shouldHighlighActive={true}
|
||||
/>
|
||||
}
|
||||
<Categories/>
|
||||
</>
|
||||
);
|
||||
@@ -76,9 +79,7 @@ const CategoriesList = ({hasChannels, iconPad, isCRTEnabled, moreThanOneTeam}: C
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.container, tabletStyle]}>
|
||||
<ChannelListHeader
|
||||
iconPad={iconPad}
|
||||
/>
|
||||
<ChannelListHeader iconPad={iconPad}/>
|
||||
{content}
|
||||
</Animated.View>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,8 @@ import React, {useEffect} from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import {HOME_PADDING} from '@constants/view';
|
||||
|
||||
import SearchField from './search_field';
|
||||
import UnreadFilter from './unread_filter';
|
||||
|
||||
@@ -15,6 +17,7 @@ type Props = {
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
...HOME_PADDING,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ export type Props = {
|
||||
isMultiselect?: boolean;
|
||||
selected: SelectedDialogValue;
|
||||
theme: Theme;
|
||||
teammateNameDisplay: string;
|
||||
componentId: AvailableScreens;
|
||||
}
|
||||
|
||||
@@ -165,7 +164,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
||||
function IntegrationSelector(
|
||||
{dataSource, data, isMultiselect = false, selected, handleSelect,
|
||||
currentTeamId, currentUserId, componentId, getDynamicOptions, options, teammateNameDisplay}: Props) {
|
||||
currentTeamId, currentUserId, componentId, getDynamicOptions, options}: Props) {
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const searchTimeoutId = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -554,7 +553,6 @@ function IntegrationSelector(
|
||||
return (
|
||||
<ServerUserList
|
||||
currentUserId={currentUserId}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
term={term}
|
||||
tutorialWatched={true}
|
||||
handleSelectProfile={handleSelectProfile}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {isGuest} from '@utils/user';
|
||||
import Selection from './selection';
|
||||
import Summary from './summary';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {AvailableScreens, NavButtons} from '@typings/screens/navigation';
|
||||
import type {OptionsTopBarButton} from 'react-native-navigation';
|
||||
|
||||
@@ -69,7 +70,7 @@ const getStyleSheet = makeStyleSheetFromTheme(() => {
|
||||
|
||||
export type EmailInvite = string;
|
||||
|
||||
export type SearchResult = UserProfile|EmailInvite;
|
||||
export type SearchResult = UserProfile|UserModel|EmailInvite;
|
||||
|
||||
export type InviteResult = {
|
||||
userId: string;
|
||||
|
||||
@@ -70,6 +70,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
},
|
||||
searchListPadding: {
|
||||
paddingVertical: 8,
|
||||
flex: 1,
|
||||
},
|
||||
searchListShadow: {
|
||||
shadowColor: '#000',
|
||||
@@ -85,6 +86,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
searchListFlatList: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
selectedItems: {
|
||||
display: 'flex',
|
||||
@@ -222,12 +224,12 @@ export default function Selection({
|
||||
|
||||
style.push(styles.searchListFlatList);
|
||||
|
||||
if (searchResults.length) {
|
||||
if (searchResults.length || (term && !loading)) {
|
||||
style.push(styles.searchListBorder, styles.searchListPadding);
|
||||
}
|
||||
|
||||
return style;
|
||||
}, [searchResults, styles]);
|
||||
}, [searchResults, styles, Boolean(term && !loading)]);
|
||||
|
||||
const renderNoResults = useCallback(() => {
|
||||
if (!term || loading) {
|
||||
@@ -235,20 +237,18 @@ export default function Selection({
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.searchListBorder, styles.searchListPadding]}>
|
||||
<TextItem
|
||||
text={term}
|
||||
type={TextItemType.SEARCH_NO_RESULTS}
|
||||
testID='invite.search_list_no_results'
|
||||
/>
|
||||
</View>
|
||||
<TextItem
|
||||
text={term}
|
||||
type={TextItemType.SEARCH_NO_RESULTS}
|
||||
testID='invite.search_list_no_results'
|
||||
/>
|
||||
);
|
||||
}, [term, loading]);
|
||||
|
||||
const renderItem = useCallback(({item}: ListRenderItemInfo<SearchResult>) => {
|
||||
const key = keyExtractor(item);
|
||||
|
||||
return (
|
||||
return typeof item === 'string' ? (
|
||||
<TouchableWithFeedback
|
||||
key={key}
|
||||
index={key}
|
||||
@@ -257,19 +257,18 @@ export default function Selection({
|
||||
type='native'
|
||||
testID={`invite.search_list_item.${key}`}
|
||||
>
|
||||
{typeof item === 'string' ? (
|
||||
<TextItem
|
||||
text={item}
|
||||
type={TextItemType.SEARCH_INVITE}
|
||||
testID='invite.search_list_text_item'
|
||||
/>
|
||||
) : (
|
||||
<UserItem
|
||||
user={item}
|
||||
testID='invite.search_list_user_item'
|
||||
/>
|
||||
)}
|
||||
<TextItem
|
||||
text={item}
|
||||
type={TextItemType.SEARCH_INVITE}
|
||||
testID='invite.search_list_text_item'
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
) : (
|
||||
<UserItem
|
||||
user={item}
|
||||
testID='invite.search_list_user_item'
|
||||
onUserPress={onSelectItem}
|
||||
/>
|
||||
);
|
||||
}, [searchResults, onSelectItem]);
|
||||
|
||||
|
||||
@@ -46,11 +46,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
flexDirection: 'column',
|
||||
paddingVertical: 12,
|
||||
},
|
||||
user: {
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
height: 'auto',
|
||||
},
|
||||
reason: {
|
||||
paddingLeft: 56,
|
||||
paddingRight: 20,
|
||||
@@ -135,7 +130,6 @@ export default function SummaryReport({
|
||||
) : (
|
||||
<UserItem
|
||||
user={item}
|
||||
containerStyle={styles.user}
|
||||
testID={`${testID}.user_item`}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,6 @@ import {typography} from '@utils/typography';
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
item: {
|
||||
paddingHorizontal: 20,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -21,7 +20,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
search: {
|
||||
height: 40,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
itemText: {
|
||||
display: 'flex',
|
||||
|
||||
@@ -10,7 +10,7 @@ import {observeTutorialWatched} from '@queries/app/global';
|
||||
import {observeCurrentChannel} from '@queries/servers/channel';
|
||||
import {observeCanManageChannelMembers, observePermissionForChannel} from '@queries/servers/role';
|
||||
import {observeCurrentChannelId, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeCurrentUser, observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
|
||||
import ManageChannelMembers from './manage_channel_members';
|
||||
|
||||
@@ -31,7 +31,6 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
currentUserId: observeCurrentUserId(database),
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
canManageAndRemoveMembers,
|
||||
teammateNameDisplay: observeTeammateNameDisplay(database),
|
||||
tutorialWatched: observeTutorialWatched(Tutorial.PROFILE_LONG_PRESS),
|
||||
canChangeMemberRoles,
|
||||
};
|
||||
|
||||
@@ -30,7 +30,6 @@ type Props = {
|
||||
componentId: AvailableScreens;
|
||||
currentTeamId: string;
|
||||
currentUserId: string;
|
||||
teammateNameDisplay: string;
|
||||
tutorialWatched: boolean;
|
||||
}
|
||||
|
||||
@@ -69,7 +68,6 @@ export default function ManageChannelMembers({
|
||||
componentId,
|
||||
currentTeamId,
|
||||
currentUserId,
|
||||
teammateNameDisplay,
|
||||
tutorialWatched,
|
||||
}: Props) {
|
||||
const serverUrl = useServerUrl();
|
||||
@@ -271,7 +269,6 @@ export default function ManageChannelMembers({
|
||||
selectedIds={EMPTY_IDS}
|
||||
showManageMode={canManageAndRemoveMembers && isManageMode}
|
||||
showNoResults={!loading}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
term={term}
|
||||
testID='manage_members.user_list'
|
||||
tutorialWatched={tutorialWatched}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, StyleSheet, TouchableOpacity} from 'react-native';
|
||||
import {Keyboard} from 'react-native';
|
||||
|
||||
import UserItem from '@components/user_item';
|
||||
import {Screens} from '@constants';
|
||||
@@ -15,20 +15,9 @@ import type UserModel from '@typings/database/models/servers/user';
|
||||
type Props = {
|
||||
channelId: string;
|
||||
location: string;
|
||||
user?: UserModel;
|
||||
user: UserModel;
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: 8,
|
||||
paddingLeft: 0,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
picture: {
|
||||
marginLeft: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const Reactor = ({channelId, location, user}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
@@ -46,14 +35,11 @@ const Reactor = ({channelId, location, user}: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={openUserProfile}>
|
||||
<UserItem
|
||||
containerStyle={style.container}
|
||||
pictureContainerStyle={style.picture}
|
||||
user={user}
|
||||
testID='reactions.reactor_item'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<UserItem
|
||||
user={user}
|
||||
testID='reactions.reactor_item'
|
||||
onUserPress={openUserProfile}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ const UserProfileCustomStatus = ({customStatus}: Props) => {
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus}
|
||||
emojiSize={24}
|
||||
testID={'user_profile.custom_status_emoji'}
|
||||
style={styles.emoji}
|
||||
/>
|
||||
}
|
||||
|
||||
6
app/utils/strings.ts
Normal file
6
app/utils/strings.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export function nonBreakingString(s: string) {
|
||||
return s.replace(' ', '\xa0');
|
||||
}
|
||||
@@ -5,13 +5,18 @@ import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji
|
||||
import type {StyleProp, TextStyle} from 'react-native';
|
||||
import type {ImageStyle} from 'react-native-fast-image';
|
||||
|
||||
// The intersection of the image styles and text styles
|
||||
type ImageStyleUniques = Omit<ImageStyle, keyof(TextStyle)>
|
||||
export type EmojiCommonStyle = Omit<ImageStyle, keyof(ImageStyleUniques)>
|
||||
|
||||
export type EmojiProps = {
|
||||
emojiName: string;
|
||||
displayTextOnly?: boolean;
|
||||
literal?: string;
|
||||
size?: number;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
customEmojiStyle?: StyleProp<ImageStyle>;
|
||||
imageStyle?: StyleProp<ImageStyle>;
|
||||
commonStyle?: StyleProp<EmojiCommonStyle>;
|
||||
customEmojis: CustomEmojiModel[];
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user