forked from Ivasoft/mattermost-mobile
Add selected users panel
This commit is contained in:
@@ -7,7 +7,6 @@ import Animated, {useAnimatedStyle, useDerivedValue, useSharedValue, withTiming}
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import Toast from '@components/toast';
|
||||
import {General} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet, useKeyboardHeightWithDuration} from '@hooks/device';
|
||||
import Button from '@screens/bottom_sheet/button';
|
||||
@@ -275,7 +274,6 @@ export default function SelectedUsers({
|
||||
onPress={handlePress}
|
||||
icon={buttonIcon}
|
||||
text={buttonText}
|
||||
disabled={numberSelectedIds > General.MAX_USERS_IN_GM}
|
||||
/>
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
|
||||
@@ -29,6 +29,7 @@ export default {
|
||||
SHOW_FULLNAME: 'full_name',
|
||||
},
|
||||
SPECIAL_MENTIONS: new Set(['all', 'channel', 'here']),
|
||||
MAX_USERS_ADD_TO_CHANNEL: 25,
|
||||
MAX_USERS_IN_GM: 7,
|
||||
MIN_USERS_IN_GM: 3,
|
||||
MAX_GROUP_CHANNELS_FOR_PROFILES: 50,
|
||||
|
||||
@@ -3,30 +3,27 @@
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Keyboard, Platform, StyleSheet, View} from 'react-native';
|
||||
import {Keyboard, LayoutChangeEvent, Platform, StyleSheet, View} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
// import SelectedUsers from '@components/selected_users_panel';
|
||||
import {addMembersToChannel} from '@actions/remote/channel';
|
||||
import {fetchProfilesNotInChannel, searchProfiles} from '@actions/remote/user';
|
||||
import Loading from '@components/loading';
|
||||
import Search from '@components/search';
|
||||
import SelectedUsers from '@components/selected_users';
|
||||
import UserList from '@components/user_list';
|
||||
import {General} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {ChannelModel} from '@database/models/server';
|
||||
import {debounce} from '@helpers/api/general';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {useModalPosition} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme} from '@utils/theme';
|
||||
import {filterProfilesMatchingTerm} from '@utils/user';
|
||||
|
||||
const ADD_BUTTON = 'add-button';
|
||||
|
||||
const close = () => {
|
||||
Keyboard.dismiss();
|
||||
popTopScreen();
|
||||
@@ -52,6 +49,10 @@ const messages = defineMessages({
|
||||
id: t('mobile.channel_add_people.title'),
|
||||
defaultMessage: 'Add Members',
|
||||
},
|
||||
toastMessage: {
|
||||
id: t('mobile.channel_add_people.max_limit_reached'),
|
||||
defaultMessage: 'Max selected users are limited to {maxCount} members',
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
@@ -64,8 +65,17 @@ type Props = {
|
||||
tutorialWatched: boolean;
|
||||
}
|
||||
|
||||
const MAX_SELECTED_USERS = General.MAX_USERS_ADD_TO_CHANNEL;
|
||||
|
||||
function removeProfileFromList(list: {[id: string]: UserProfile}, id: string) {
|
||||
const newSelectedIds = Object.assign({}, list);
|
||||
|
||||
Reflect.deleteProperty(newSelectedIds, id);
|
||||
return newSelectedIds;
|
||||
}
|
||||
|
||||
export default function ChannelAddPeople({
|
||||
componentId,
|
||||
// componentId,
|
||||
currentChannel,
|
||||
currentTeamId,
|
||||
currentUserId,
|
||||
@@ -82,6 +92,8 @@ export default function ChannelAddPeople({
|
||||
const next = useRef(true);
|
||||
const page = useRef(-1);
|
||||
const mounted = useRef(false);
|
||||
const mainView = useRef<View>(null);
|
||||
const modalPosition = useModalPosition(mainView);
|
||||
|
||||
const [profiles, setProfiles] = useState<UserProfile[]>([]);
|
||||
const [searchResults, setSearchResults] = useState<UserProfile[]>([]);
|
||||
@@ -89,6 +101,9 @@ export default function ChannelAddPeople({
|
||||
const [term, setTerm] = useState('');
|
||||
const [startingAddPeople, setStartingAddPeople] = useState(false);
|
||||
const [selectedIds, setSelectedIds] = useState<{[id: string]: UserProfile}>({});
|
||||
const [containerHeight, setContainerHeight] = useState(0);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
|
||||
const selectedCount = Object.keys(selectedIds).length;
|
||||
const groupConstrained = currentChannel.isGroupConstrained;
|
||||
const currentChannelId = currentChannel.id;
|
||||
@@ -121,11 +136,7 @@ export default function ChannelAddPeople({
|
||||
}, 100), [loading, isSearch, serverUrl, currentTeamId]);
|
||||
|
||||
const handleRemoveProfile = useCallback((id: string) => {
|
||||
const newSelectedIds = Object.assign({}, selectedIds);
|
||||
|
||||
Reflect.deleteProperty(newSelectedIds, id);
|
||||
|
||||
setSelectedIds(newSelectedIds);
|
||||
setSelectedIds((current) => removeProfileFromList(current, id));
|
||||
}, [selectedIds]);
|
||||
|
||||
const addPeopleToChannel = useCallback(async (ids: string[]): Promise<boolean> => {
|
||||
@@ -167,17 +178,27 @@ export default function ChannelAddPeople({
|
||||
}, [startingAddPeople, selectedIds, addPeopleToChannel]);
|
||||
|
||||
const handleSelectProfile = useCallback((user: UserProfile) => {
|
||||
if (selectedIds[user.id]) {
|
||||
handleRemoveProfile(user.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const newSelectedIds = Object.assign({}, selectedIds);
|
||||
newSelectedIds[user.id] = user;
|
||||
|
||||
setSelectedIds(newSelectedIds);
|
||||
clearSearch();
|
||||
}, [selectedIds, handleRemoveProfile, startAddPeople, clearSearch]);
|
||||
setSelectedIds((current) => {
|
||||
if (current[user.id]) {
|
||||
return removeProfileFromList(current, user.id);
|
||||
}
|
||||
|
||||
const wasSelected = current[user.id];
|
||||
|
||||
if (!wasSelected && selectedCount >= MAX_SELECTED_USERS) {
|
||||
setShowToast(true);
|
||||
return current;
|
||||
}
|
||||
|
||||
const newSelectedIds = Object.assign({}, current);
|
||||
if (!wasSelected) {
|
||||
newSelectedIds[user.id] = user;
|
||||
}
|
||||
|
||||
return newSelectedIds;
|
||||
});
|
||||
}, [clearSearch, selectedIds, startAddPeople]);
|
||||
|
||||
const searchUsers = useCallback(async (searchTerm: string) => {
|
||||
const lowerCasedTerm = searchTerm.toLowerCase();
|
||||
@@ -218,36 +239,21 @@ export default function ChannelAddPeople({
|
||||
}
|
||||
}, [searchUsers, clearSearch]);
|
||||
|
||||
const updateNavigationButtons = useCallback(async (startEnabled: boolean) => {
|
||||
if (hasProfiles) {
|
||||
setButtons(componentId, {
|
||||
rightButtons: [{
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
id: ADD_BUTTON,
|
||||
text: formatMessage({id: 'mobile.channel_add_people.button', defaultMessage: 'Add'}),
|
||||
showAsAction: 'always',
|
||||
enabled: startEnabled,
|
||||
testID: 'add_members.start.button',
|
||||
}],
|
||||
});
|
||||
}
|
||||
}, [intl.locale, hasProfiles, theme]);
|
||||
|
||||
useNavButtonPressed(ADD_BUTTON, componentId, startAddPeople, [startAddPeople]);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
updateNavigationButtons(false);
|
||||
getProfiles();
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useDidUpdate(() => {
|
||||
const canStart = selectedCount > 0 && !startingAddPeople;
|
||||
updateNavigationButtons(canStart);
|
||||
}, [selectedCount > 0, startingAddPeople, updateNavigationButtons]);
|
||||
useEffect(() => {
|
||||
setShowToast(selectedCount >= MAX_SELECTED_USERS);
|
||||
}, [selectedCount >= MAX_SELECTED_USERS]);
|
||||
|
||||
const onLayout = useCallback((e: LayoutChangeEvent) => {
|
||||
setContainerHeight(e.nativeEvent.layout.height);
|
||||
}, []);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (isSearch) {
|
||||
@@ -283,6 +289,8 @@ export default function ChannelAddPeople({
|
||||
<SafeAreaView
|
||||
style={style.container}
|
||||
testID='add_members.screen'
|
||||
onLayout={onLayout}
|
||||
edges={['top', 'left', 'right']}
|
||||
>
|
||||
{hasProfiles &&
|
||||
<View style={style.searchBar}>
|
||||
@@ -300,20 +308,6 @@ export default function ChannelAddPeople({
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
{/*
|
||||
https://mattermost.atlassian.net/browse/MM-48489
|
||||
V1 does not have the selected users modal.
|
||||
Add this back in after build the scrollable selectable users panel
|
||||
*/}
|
||||
{/* {selectedCount > 0 &&
|
||||
<SelectedUsers
|
||||
selectedIds={selectedIds}
|
||||
warnCount={General.MAX_ADD_USERS - 2}
|
||||
maxCount={General.MAX_ADD_USERS}
|
||||
onRemove={handleRemoveProfile}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
/>
|
||||
} */}
|
||||
<UserList
|
||||
currentUserId={currentUserId}
|
||||
handleSelectProfile={handleSelectProfile}
|
||||
@@ -327,6 +321,20 @@ export default function ChannelAddPeople({
|
||||
testID='add_members.user_list'
|
||||
tutorialWatched={tutorialWatched}
|
||||
/>
|
||||
<SelectedUsers
|
||||
containerHeight={containerHeight}
|
||||
modalPosition={modalPosition}
|
||||
showToast={showToast}
|
||||
setShowToast={setShowToast}
|
||||
toastIcon={'check'}
|
||||
toastMessage={formatMessage(messages.toastMessage, {maxCount: MAX_SELECTED_USERS})}
|
||||
selectedIds={selectedIds}
|
||||
onRemove={handleRemoveProfile}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
onPress={startAddPeople}
|
||||
buttonIcon={'account-plus-outline'}
|
||||
buttonText={formatMessage(messages.button)}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Keyboard, LayoutChangeEvent, Platform, View} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
@@ -56,6 +56,9 @@ type Props = {
|
||||
tutorialWatched: boolean;
|
||||
}
|
||||
|
||||
const MAX_SELECTED_USERS = General.MAX_USERS_IN_GM;
|
||||
const EMPTY: UserProfile[] = [];
|
||||
|
||||
const close = () => {
|
||||
Keyboard.dismiss();
|
||||
dismissModal();
|
||||
@@ -93,13 +96,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
};
|
||||
});
|
||||
|
||||
function reduceProfiles(state: UserProfile[], action: {type: 'add'; values?: UserProfile[]}) {
|
||||
if (action.type === 'add' && action.values?.length) {
|
||||
return [...state, ...action.values];
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function removeProfileFromList(list: {[id: string]: UserProfile}, id: string) {
|
||||
const newSelectedIds = Object.assign({}, list);
|
||||
|
||||
@@ -128,8 +124,8 @@ export default function CreateDirectMessage({
|
||||
const mainView = useRef<View>(null);
|
||||
const modalPosition = useModalPosition(mainView);
|
||||
|
||||
const [profiles, dispatchProfiles] = useReducer(reduceProfiles, []);
|
||||
const [searchResults, setSearchResults] = useState<UserProfile[]>([]);
|
||||
const [profiles, setProfiles] = useState<UserProfile[]>(EMPTY);
|
||||
const [searchResults, setSearchResults] = useState<UserProfile[]>(EMPTY);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [term, setTerm] = useState('');
|
||||
const [startingConversation, setStartingConversation] = useState(false);
|
||||
@@ -140,7 +136,7 @@ export default function CreateDirectMessage({
|
||||
|
||||
const isSearch = Boolean(term);
|
||||
|
||||
const loadedProfiles = ({users}: {users?: UserProfile[]}) => {
|
||||
const loadedProfiles = ({users}: {users: UserProfile[]}) => {
|
||||
if (mounted.current) {
|
||||
if (users && !users.length) {
|
||||
next.current = false;
|
||||
@@ -148,13 +144,13 @@ export default function CreateDirectMessage({
|
||||
|
||||
page.current += 1;
|
||||
setLoading(false);
|
||||
dispatchProfiles({type: 'add', values: users});
|
||||
setProfiles((prev: UserProfile[]) => [...prev, ...users]);
|
||||
}
|
||||
};
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (term) {
|
||||
const exactMatches: UserProfile[] = [];
|
||||
const exactMatches: UserProfile[] = EMPTY;
|
||||
const filterByTerm = (p: UserProfile) => {
|
||||
if (selectedCount > 0 && p.id === currentUserId) {
|
||||
return false;
|
||||
@@ -247,29 +243,30 @@ export default function CreateDirectMessage({
|
||||
};
|
||||
|
||||
startConversation(selectedId);
|
||||
} else {
|
||||
clearSearch();
|
||||
setSelectedIds((current) => {
|
||||
if (current[user.id]) {
|
||||
return removeProfileFromList(current, user.id);
|
||||
}
|
||||
|
||||
const wasSelected = current[user.id];
|
||||
|
||||
if (!wasSelected && selectedCount >= General.MAX_USERS_IN_GM) {
|
||||
setShowToast(true);
|
||||
return current;
|
||||
}
|
||||
|
||||
const newSelectedIds = Object.assign({}, current);
|
||||
if (!wasSelected) {
|
||||
newSelectedIds[user.id] = user;
|
||||
}
|
||||
|
||||
return newSelectedIds;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}, [currentUserId, clearSearch]);
|
||||
|
||||
clearSearch();
|
||||
setSelectedIds((current) => {
|
||||
if (current[user.id]) {
|
||||
return removeProfileFromList(current, user.id);
|
||||
}
|
||||
|
||||
const wasSelected = current[user.id];
|
||||
|
||||
if (!wasSelected && selectedCount >= MAX_SELECTED_USERS) {
|
||||
setShowToast(true);
|
||||
return current;
|
||||
}
|
||||
|
||||
const newSelectedIds = Object.assign({}, current);
|
||||
if (!wasSelected) {
|
||||
newSelectedIds[user.id] = user;
|
||||
}
|
||||
|
||||
return newSelectedIds;
|
||||
});
|
||||
}, [currentUserId, clearSearch, selectedCount]);
|
||||
|
||||
const searchUsers = useCallback(async (searchTerm: string) => {
|
||||
const lowerCasedTerm = searchTerm.toLowerCase();
|
||||
@@ -282,7 +279,7 @@ export default function CreateDirectMessage({
|
||||
results = await searchProfiles(serverUrl, lowerCasedTerm, {allow_inactive: true});
|
||||
}
|
||||
|
||||
let searchData: UserProfile[] = [];
|
||||
let searchData: UserProfile[] = EMPTY;
|
||||
if (results.data) {
|
||||
searchData = results.data;
|
||||
}
|
||||
@@ -338,8 +335,8 @@ export default function CreateDirectMessage({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setShowToast(selectedCount >= General.MAX_USERS_IN_GM);
|
||||
}, [selectedCount >= General.MAX_USERS_IN_GM]);
|
||||
setShowToast(selectedCount >= MAX_SELECTED_USERS);
|
||||
}, [selectedCount >= MAX_SELECTED_USERS]);
|
||||
|
||||
if (startingConversation) {
|
||||
return (
|
||||
@@ -390,7 +387,7 @@ export default function CreateDirectMessage({
|
||||
showToast={showToast}
|
||||
setShowToast={setShowToast}
|
||||
toastIcon={'check'}
|
||||
toastMessage={formatMessage(messages.toastMessage, {maxCount: General.MAX_USERS_IN_GM})}
|
||||
toastMessage={formatMessage(messages.toastMessage, {maxCount: MAX_SELECTED_USERS})}
|
||||
selectedIds={selectedIds}
|
||||
onRemove={handleRemoveProfile}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
|
||||
@@ -428,6 +428,7 @@
|
||||
"mobile.channel_add_people.error": "We could not add those users to the channel. Please check your connection and try again.",
|
||||
"mobile.channel_add_people.title": "Add Members",
|
||||
"mobile.channel_add_people.button": "Add",
|
||||
"mobile.channel_add_people.max_limit_reached": "Max selected users are limited to {maxCount} members",
|
||||
"mobile.channel_info.alertNo": "No",
|
||||
"mobile.channel_info.alertYes": "Yes",
|
||||
"mobile.channel_list.recent": "Recent",
|
||||
|
||||
Reference in New Issue
Block a user