Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/autocomplete/PeopleAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import React, { PureComponent } from 'react';
import { SectionList } from 'react-native';

import type { User, UserId, UserGroup, Dispatch } from '../types';
import type { MutedUsersState, User, UserId, UserGroup, Dispatch } from '../types';
import { connect } from '../react-redux';
import { getSortedUsers, getUserGroups } from '../selectors';
import { getMutedUsers, getSortedUsers, getUserGroups } from '../selectors';
import {
type AutocompleteOption,
getAutocompleteSuggestion,
Expand All @@ -20,6 +20,7 @@ type Props = $ReadOnly<{|
dispatch: Dispatch,
filter: string,
onAutocomplete: (name: string) => void,
mutedUsers: MutedUsersState,
ownUserId: UserId,
users: User[],
userGroups: UserGroup[],
Expand All @@ -44,9 +45,9 @@ class PeopleAutocomplete extends PureComponent<Props> {
};

render() {
const { filter, ownUserId, users, userGroups } = this.props;
const { filter, mutedUsers, ownUserId, users, userGroups } = this.props;
const filteredUserGroups = getAutocompleteUserGroupSuggestions(userGroups, filter);
const filteredUsers = getAutocompleteSuggestion(users, filter, ownUserId);
const filteredUsers = getAutocompleteSuggestion(users, filter, ownUserId, mutedUsers);

if (filteredUserGroups.length + filteredUsers.length === 0) {
return null;
Expand Down Expand Up @@ -105,6 +106,7 @@ class PeopleAutocomplete extends PureComponent<Props> {
}

export default connect(state => ({
mutedUsers: getMutedUsers(state),
ownUserId: getOwnUserId(state),
users: getSortedUsers(state),
userGroups: getUserGroups(state),
Expand Down
83 changes: 46 additions & 37 deletions src/pm-conversations/PmConversationList.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/* @flow strict-local */
import React, { PureComponent } from 'react';
import React, { useCallback } from 'react';
import { FlatList } from 'react-native';
import { useDispatch, useSelector } from '../react-redux';

import type { Dispatch, PmConversationData, UserOrBot } from '../types';
import type { PmConversationData, UserOrBot } from '../types';
import { createStyleSheet } from '../styles';
import { type PmKeyUsers } from '../utils/recipient';
import { pm1to1NarrowFromUser, pmNarrowFromUsers } from '../utils/narrow';
import UserItem from '../users/UserItem';
import GroupPmConversationItem from './GroupPmConversationItem';
import { doNarrow } from '../actions';
import { getMutedUsers } from '../selectors';

const styles = createStyleSheet({
list: {
Expand All @@ -18,52 +20,59 @@ const styles = createStyleSheet({
});

type Props = $ReadOnly<{|
dispatch: Dispatch,
conversations: PmConversationData[],
|}>;

/**
* A list describing all PM conversations.
* */
export default class PmConversationList extends PureComponent<Props> {
handleUserNarrow = (user: UserOrBot) => {
this.props.dispatch(doNarrow(pm1to1NarrowFromUser(user)));
};
export default function PmConversationList(props: Props) {
const dispatch = useDispatch();

handleGroupNarrow = (users: PmKeyUsers) => {
this.props.dispatch(doNarrow(pmNarrowFromUsers(users)));
};
const handleUserNarrow = useCallback(
(user: UserOrBot) => {
dispatch(doNarrow(pm1to1NarrowFromUser(user)));
},
[dispatch],
);

render() {
const { conversations } = this.props;
const handleGroupNarrow = useCallback(
(users: PmKeyUsers) => {
dispatch(doNarrow(pmNarrowFromUsers(users)));
},
[dispatch],
);

return (
<FlatList
style={styles.list}
initialNumToRender={20}
data={conversations}
keyExtractor={item => item.key}
renderItem={({ item }) => {
const users = item.keyRecipients;
if (users.length === 1) {
return (
<UserItem
userId={users[0].user_id}
unreadCount={item.unread}
onPress={this.handleUserNarrow}
/>
);
const { conversations } = props;
const mutedUsers = useSelector(getMutedUsers);

return (
<FlatList
style={styles.list}
initialNumToRender={20}
data={conversations}
keyExtractor={item => item.key}
renderItem={({ item }) => {
const users = item.keyRecipients;
if (users.length === 1) {
const user_id = users[0].user_id;
if (mutedUsers.has(user_id)) {
return null;
} else {
return (
<GroupPmConversationItem
users={users}
unreadCount={item.unread}
onPress={this.handleGroupNarrow}
/>
<UserItem userId={user_id} unreadCount={item.unread} onPress={handleUserNarrow} />
);
}
}}
/>
);
}
} else {
return (
<GroupPmConversationItem
users={users}
unreadCount={item.unread}
onPress={handleGroupNarrow}
/>
);
}
}}
/>
);
}
5 changes: 2 additions & 3 deletions src/pm-conversations/PmConversationsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { RouteProp } from '../react-navigation';
import type { MainTabsNavigationProp } from '../main/MainTabsScreen';
import * as NavigationService from '../nav/NavigationService';
import { ThemeContext, createStyleSheet } from '../styles';
import { useSelector, useDispatch } from '../react-redux';
import { useSelector } from '../react-redux';
import { Label, ZulipButton, LoadingBanner } from '../common';
import { IconPeople, IconSearch } from '../common/Icons';
import PmConversationList from './PmConversationList';
Expand Down Expand Up @@ -43,7 +43,6 @@ type Props = $ReadOnly<{|
* */
export default function PmConversationsScreen(props: Props) {
const conversations = useSelector(getRecentConversations);
const dispatch = useDispatch();
const context = useContext(ThemeContext);

return (
Expand Down Expand Up @@ -72,7 +71,7 @@ export default function PmConversationsScreen(props: Props) {
{conversations.length === 0 ? (
<Label style={styles.emptySlate} text="No recent conversations" />
) : (
<PmConversationList dispatch={dispatch} conversations={conversations} />
<PmConversationList conversations={conversations} />
)}
</View>
);
Expand Down
2 changes: 1 addition & 1 deletion src/unread/UnreadCards.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function UnreadCards(props: Props) {
const unreadCards: Array<Card> = [
{
key: 'private',
data: [{ conversations, dispatch }],
data: [{ conversations }],
},
...unreadStreamsAndTopics,
];
Expand Down
84 changes: 41 additions & 43 deletions src/users/UserList.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/* @flow strict-local */
import React, { PureComponent } from 'react';
import React from 'react';
import { SectionList } from 'react-native';
import { useSelector } from '../react-redux';

import type { PresenceState, UserOrBot } from '../types';
import { createStyleSheet } from '../styles';
import { SectionHeader, SearchEmptyState } from '../common';
import UserItem from './UserItem';
import { sortUserList, filterUserList, groupUsersByStatus } from './userHelpers';
import { getMutedUsers } from '../selectors';

const styles = createStyleSheet({
list: {
Expand All @@ -17,53 +19,49 @@ const styles = createStyleSheet({
type Props = $ReadOnly<{|
filter: string,
users: $ReadOnlyArray<UserOrBot>,
selected: $ReadOnlyArray<UserOrBot>,
selected?: $ReadOnlyArray<UserOrBot>,
presences: PresenceState,
onPress: (user: UserOrBot) => void,
|}>;

export default class UserList extends PureComponent<Props> {
static defaultProps = {
selected: [],
};
export default function UserList(props: Props) {
const { filter, users, presences, onPress, selected = [] } = props;
const mutedUsers = useSelector(getMutedUsers);
const filteredUsers = filterUserList(users, filter).filter(user => !mutedUsers.has(user.user_id));

render() {
const { filter, users, presences, onPress, selected } = this.props;
const shownUsers = sortUserList(filterUserList(users, filter), presences);

if (shownUsers.length === 0) {
return <SearchEmptyState text="No users found" />;
}
if (filteredUsers.length === 0) {
return <SearchEmptyState text="No users found" />;
}

const groupedUsers = groupUsersByStatus(shownUsers, presences);
const sections = Object.keys(groupedUsers).map(key => ({
key: `${key.charAt(0).toUpperCase()}${key.slice(1)}`,
data: groupedUsers[key].map(u => u.user_id),
}));
const sortedUsers = sortUserList(filteredUsers, presences);
const groupedUsers = groupUsersByStatus(sortedUsers, presences);
const sections = Object.keys(groupedUsers).map(key => ({
key: `${key.charAt(0).toUpperCase()}${key.slice(1)}`,
data: groupedUsers[key].map(u => u.user_id),
}));

return (
<SectionList
style={styles.list}
stickySectionHeadersEnabled
keyboardShouldPersistTaps="always"
initialNumToRender={20}
sections={sections}
keyExtractor={item => item}
renderItem={({ item }) => (
<UserItem
key={item}
userId={item}
onPress={onPress}
isSelected={!!selected.find(user => user.user_id === item)}
/>
)}
renderSectionHeader={({ section }) =>
section.data.length === 0 ? null : (
// $FlowFixMe[incompatible-type]
<SectionHeader text={section.key} />
)
}
/>
);
}
return (
<SectionList
style={styles.list}
stickySectionHeadersEnabled
keyboardShouldPersistTaps="always"
initialNumToRender={20}
sections={sections}
keyExtractor={item => item}
renderItem={({ item }) => (
<UserItem
key={item}
userId={item}
onPress={onPress}
isSelected={!!selected.find(user => user.user_id === item)}
/>
)}
renderSectionHeader={({ section }) =>
section.data.length === 0 ? null : (
// $FlowFixMe[incompatible-type]
<SectionHeader text={section.key} />
)
}
/>
);
}
37 changes: 33 additions & 4 deletions src/users/__tests__/userHelpers-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* @flow strict-local */
import deepFreeze from 'deep-freeze';
import Immutable from 'immutable';

import {
sortUserList,
Expand Down Expand Up @@ -61,7 +62,12 @@ describe('getAutocompleteSuggestion', () => {
test('empty input results in empty list', () => {
const users = deepFreeze([]);

const filteredUsers = getAutocompleteSuggestion(users, 'some filter', eg.selfUser.user_id);
const filteredUsers = getAutocompleteSuggestion(
users,
'some filter',
eg.selfUser.user_id,
Immutable.Map(),
);
expect(filteredUsers).toBe(users);
});

Expand All @@ -74,7 +80,20 @@ describe('getAutocompleteSuggestion', () => {
{ user_id: -1, full_name: 'all', email: '(Notify everyone)' },
someGuyUser,
];
const filteredUsers = getAutocompleteSuggestion(users, '', meUser.user_id);
const filteredUsers = getAutocompleteSuggestion(users, '', meUser.user_id, Immutable.Map());
expect(filteredUsers).toEqual(shouldMatch);
});

test('filters out muted user', () => {
const mutedUser = eg.makeUser({ name: 'Muted User' });
const meUser = eg.makeUser({ name: 'Me' });
const users = deepFreeze([mutedUser, meUser]);

const mutedUsers = Immutable.Map([[mutedUser.user_id, 0]]);

const shouldMatch = [{ user_id: -1, full_name: 'all', email: '(Notify everyone)' }];

const filteredUsers = getAutocompleteSuggestion(users, '', meUser.user_id, mutedUsers);
expect(filteredUsers).toEqual(shouldMatch);
});

Expand All @@ -87,7 +106,12 @@ describe('getAutocompleteSuggestion', () => {
const allUsers = deepFreeze([user1, user2, user3, user4, user5]);

const shouldMatch = [user1, user2, user3, user5];
const filteredUsers = getAutocompleteSuggestion(allUsers, 'match', eg.selfUser.user_id);
const filteredUsers = getAutocompleteSuggestion(
allUsers,
'match',
eg.selfUser.user_id,
Immutable.Map(),
);
expect(filteredUsers).toEqual(shouldMatch);
});

Expand Down Expand Up @@ -127,7 +151,12 @@ describe('getAutocompleteSuggestion', () => {
user11, // have priority because of 'ma' contains in name
user4, // email contains 'ma'
];
const filteredUsers = getAutocompleteSuggestion(allUsers, 'ma', eg.selfUser.user_id);
const filteredUsers = getAutocompleteSuggestion(
allUsers,
'ma',
eg.selfUser.user_id,
Immutable.Map(),
);
expect(filteredUsers).toEqual(shouldMatch);
});
});
Expand Down
Loading