[IMPROVE] Redesign create room flow (#4381)

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Reinaldo Neto 2022-08-26 16:16:45 -03:00 committed by GitHub
parent cbc6892084
commit 9cbffff248
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1532 additions and 1167 deletions

View File

@ -22,6 +22,7 @@ const getStories = () => {
require("../app/containers/Avatar/Avatar.stories.tsx"), require("../app/containers/Avatar/Avatar.stories.tsx"),
require("../app/containers/BackgroundContainer/index.stories.tsx"), require("../app/containers/BackgroundContainer/index.stories.tsx"),
require("../app/containers/Button/Button.stories.tsx"), require("../app/containers/Button/Button.stories.tsx"),
require("../app/containers/Chip/Chip.stories.tsx"),
require("../app/containers/HeaderButton/HeaderButtons.stories.tsx"), require("../app/containers/HeaderButton/HeaderButtons.stories.tsx"),
require("../app/containers/List/List.stories.tsx"), require("../app/containers/List/List.stories.tsx"),
require("../app/containers/LoginServices/LoginServices.stories.tsx"), require("../app/containers/LoginServices/LoginServices.stories.tsx"),
@ -38,6 +39,7 @@ const getStories = () => {
require("../app/containers/UIKit/UiKitModal.stories.tsx"), require("../app/containers/UIKit/UiKitModal.stories.tsx"),
require("../app/containers/UnreadBadge/UnreadBadge.stories.tsx"), require("../app/containers/UnreadBadge/UnreadBadge.stories.tsx"),
require("../app/views/CannedResponsesListView/CannedResponseItem.stories.tsx"), require("../app/views/CannedResponsesListView/CannedResponseItem.stories.tsx"),
require("../app/views/CreateChannelView/RoomSettings/SwitchItem.stories.tsx"),
require("../app/views/DiscussionsView/Item.stories.tsx"), require("../app/views/DiscussionsView/Item.stories.tsx"),
require("../app/views/RoomView/LoadMore/LoadMore.stories.tsx"), require("../app/views/RoomView/LoadMore/LoadMore.stories.tsx"),
require("../app/views/ThreadMessagesView/Item.stories.tsx"), require("../app/views/ThreadMessagesView/Item.stories.tsx"),

View File

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Chip Chip Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Rocket.Cat\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip With Short Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Short\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Avatar 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Avatar And Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar and Icon\\"]}]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Icon\\"]}]}]}]}]}"`;

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots SwitchItem Switch 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"minHeight\\":54,\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"space-between\\",\\"flexDirection\\":\\"row\\",\\"maxHeight\\":80,\\"marginBottom\\":12},{\\"backgroundColor\\":\\"#ffffff\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"marginRight\\":8}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Welcome to Rocket.Chat\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"testID\\":\\"create-channel-switch-id-hint\\",\\"style\\":[{\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"Only authorized users can write new messages\\"]}]},{\\"type\\":\\"RCTSwitch\\",\\"props\\":{\\"testID\\":\\"create-channel-switch-id\\",\\"disabled\\":false,\\"onTintColor\\":\\"#2de0a5\\",\\"style\\":{\\"height\\":31,\\"width\\":51},\\"tintColor\\":\\"#f5455c\\",\\"value\\":false,\\"accessibilityRole\\":\\"switch\\"},\\"children\\":null}]}]}"`;

View File

@ -0,0 +1,32 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import Chip, { IChip } from './index';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'flex-start',
padding: 16
}
});
export default {
title: 'Chip'
};
const ChipWrapped = ({ avatar, text, onPress, testID, style }: IChip) => (
<View style={styles.container}>
<Chip avatar={avatar} text={text} onPress={onPress} testID={testID} style={style} />
</View>
);
export const ChipText = () => <ChipWrapped avatar='rocket.cat' text={'Rocket.Cat'} onPress={() => {}} />;
export const ChipWithShortText = () => <ChipWrapped avatar='rocket.cat' text={'Short'} onPress={() => {}} />;
export const ChipWithoutAvatar = () => <ChipWrapped text={'Without Avatar'} onPress={() => {}} />;
export const ChipWithoutIcon = () => <ChipWrapped avatar='rocket.cat' text='Without Icon' />;
export const ChipWithoutAvatarAndIcon = () => <ChipWrapped text='Without Avatar and Icon' />;

View File

@ -0,0 +1,58 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import Chip, { IChip } from '.';
import { ISelectedUser } from '../../reducers/selectedUsers';
import { mockedStore as store } from '../../reducers/mockedStore';
const onPressMock = jest.fn((item: any) => item);
const testChip = {
testID: 'test-chip-id',
item: { fname: 'rocket.chat', name: 'rocket.chat' } as ISelectedUser,
onPress: onPressMock
};
const Render = ({ testID, text, avatar, onPress }: IChip) => (
<Provider store={store}>
<Chip testID={testID} text={text} onPress={onPress} avatar={avatar} />
</Provider>
);
describe('Chips', () => {
it('should render the Chip component', () => {
const { findByTestId } = render(
<Render
text={testChip.item.fname}
avatar={testChip.item.name}
testID={testChip.testID}
onPress={() => testChip.onPress(testChip.item)}
/>
);
expect(findByTestId(testChip.testID)).toBeTruthy();
});
it("should not call onPress if it's not passed", async () => {
const { findByTestId } = render(<Render text={testChip.item.fname} avatar={testChip.item.name} testID={testChip.testID} />);
const component = await findByTestId(testChip.testID);
fireEvent.press(component);
expect(onPressMock).not.toHaveBeenCalled();
});
it('should tap Chip and return item', async () => {
const { findByTestId } = render(
<Render
text={testChip.item.fname}
avatar={testChip.item.name}
testID={testChip.testID}
onPress={() => testChip.onPress(testChip.item)}
/>
);
const component = await findByTestId(testChip.testID);
fireEvent.press(component);
expect(onPressMock).toHaveBeenCalled();
expect(onPressMock).toHaveReturnedWith(testChip.item);
});
});

View File

@ -0,0 +1,75 @@
import React from 'react';
import { Pressable, StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
import { useTheme } from '../../theme';
import { CustomIcon } from '../CustomIcon';
import sharedStyles from '../../views/Styles';
import Avatar from '../Avatar';
const styles = StyleSheet.create({
pressable: {
paddingHorizontal: 8,
marginRight: 8,
borderRadius: 2,
justifyContent: 'center',
maxWidth: 192
},
container: {
flexDirection: 'row',
alignItems: 'center'
},
avatar: {
marginRight: 8,
marginVertical: 8
},
textContainer: {
marginRight: 8,
maxWidth: 120
},
name: {
fontSize: 16,
...sharedStyles.textMedium
}
});
export interface IChip {
avatar?: string;
text: string;
onPress?: Function;
testID?: string;
style?: StyleProp<ViewStyle>;
}
const Chip = ({ avatar, text, onPress, testID, style }: IChip) => {
const { colors } = useTheme();
return (
<Pressable
testID={testID}
style={({ pressed }) => [
styles.pressable,
{
backgroundColor: pressed ? colors.bannerBackground : colors.auxiliaryBackground
},
style
]}
disabled={!onPress}
onPress={() => onPress?.()}
android_ripple={{
color: colors.bannerBackground
}}
>
<View style={styles.container}>
{avatar ? <Avatar text={avatar} size={28} style={styles.avatar} /> : null}
<View style={styles.textContainer}>
<Text style={[styles.name, { color: colors.bodyText }]} numberOfLines={1}>
{text}
</Text>
</View>
{onPress ? <CustomIcon name='close' size={16} color={colors.auxiliaryTintColor} /> : null}
</View>
</Pressable>
);
};
export default Chip;

View File

@ -0,0 +1,17 @@
import React from 'react';
import { Control, Controller } from 'react-hook-form';
import { FormTextInput, IRCTextInputProps } from './FormTextInput';
interface IControlledFormTextInputProps extends IRCTextInputProps {
control: Control<any>;
name: string;
}
export const ControlledFormTextInput = ({ control, name, ...props }: IControlledFormTextInputProps) => (
<Controller
control={control}
name={name}
render={({ field: { onChange, value } }) => <FormTextInput onChangeText={onChange} value={value} {...props} />}
/>
);

View File

@ -1,2 +1,3 @@
export * from './TextInput'; export * from './TextInput';
export * from './FormTextInput'; export * from './FormTextInput';
export * from './ControlledFormTextInput';

View File

@ -25,13 +25,9 @@ const styles = StyleSheet.create({
marginRight: 15 marginRight: 15
}, },
name: { name: {
fontSize: 17, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
username: {
fontSize: 14,
...sharedStyles.textRegular
},
icon: { icon: {
marginHorizontal: 15, marginHorizontal: 15,
alignSelf: 'center' alignSelf: 'center'
@ -46,10 +42,12 @@ interface IUserItem {
onLongPress?: () => void; onLongPress?: () => void;
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
icon?: TIconsName | null; icon?: TIconsName | null;
iconColor?: string;
} }
const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon }: IUserItem): React.ReactElement => { const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, iconColor }: IUserItem) => {
const { colors } = useTheme(); const { colors } = useTheme();
return ( return (
<Pressable <Pressable
onPress={onPress} onPress={onPress}
@ -65,14 +63,11 @@ const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon }:
<View style={[styles.container, styles.button, style]}> <View style={[styles.container, styles.button, style]}>
<Avatar text={username} size={30} style={styles.avatar} /> <Avatar text={username} size={30} style={styles.avatar} />
<View style={styles.textContainer}> <View style={styles.textContainer}>
<Text style={[styles.name, { color: colors.titleText }]} numberOfLines={1}> <Text style={[styles.name, { color: colors.bodyText }]} numberOfLines={1}>
{name} {name}
</Text> </Text>
<Text style={[styles.username, { color: colors.auxiliaryText }]} numberOfLines={1}>
@{username}
</Text>
</View> </View>
{icon ? <CustomIcon name={icon} size={22} color={colors.actionTintColor} style={styles.icon} /> : null} {icon ? <CustomIcon name={icon} size={22} color={iconColor || colors.actionTintColor} style={styles.icon} /> : null}
</View> </View>
</Pressable> </Pressable>
); );

View File

@ -117,8 +117,7 @@
"Black": "أسود", "Black": "أسود",
"Block_user": "حظر المستخدم", "Block_user": "حظر المستخدم",
"Browser": "المتصفح", "Browser": "المتصفح",
"Broadcast_channel_Description": "يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد", "Broadcast_hint": "يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد",
"Broadcast_Channel": "قناة البث",
"Busy": "مشغول", "Busy": "مشغول",
"By_proceeding_you_are_agreeing": "من خلال المتابعة، أنت توافق على", "By_proceeding_you_are_agreeing": "من خلال المتابعة، أنت توافق على",
"Cancel_editing": "إلغاء التعديل", "Cancel_editing": "إلغاء التعديل",
@ -389,7 +388,6 @@
"Preferences": "التفضيلات", "Preferences": "التفضيلات",
"Preferences_saved": "تم حفظ التفضيلات", "Preferences_saved": "تم حفظ التفضيلات",
"Privacy_Policy": "سياسة الخصوصية", "Privacy_Policy": "سياسة الخصوصية",
"Private_Channel": "قناة خاصة",
"Private": "خاص", "Private": "خاص",
"Processing": "جار معالجة...", "Processing": "جار معالجة...",
"Profile_saved_successfully": "تم حفظ الملف الشخصي بنجاح!", "Profile_saved_successfully": "تم حفظ الملف الشخصي بنجاح!",
@ -403,7 +401,6 @@
"Reactions": "التفاعلات", "Reactions": "التفاعلات",
"Read_External_Permission_Message": "يحتاج Rocket.chat للوصول إلى الصور والملفات الموجودة على الجهاز", "Read_External_Permission_Message": "يحتاج Rocket.chat للوصول إلى الصور والملفات الموجودة على الجهاز",
"Read_External_Permission": "صلاحية قراءة الوسائط", "Read_External_Permission": "صلاحية قراءة الوسائط",
"Read_Only_Channel": "قناة للقراءة فقط",
"Read_Only": "قراءة فقط", "Read_Only": "قراءة فقط",
"Read_Receipt": "قراءة المستلم", "Read_Receipt": "قراءة المستلم",
"Receive_Group_Mentions": "تلقي إشارات المجموعة", "Receive_Group_Mentions": "تلقي إشارات المجموعة",

View File

@ -119,8 +119,7 @@
"Black": "Schwarz", "Black": "Schwarz",
"Block_user": "Benutzer blockieren", "Block_user": "Benutzer blockieren",
"Browser": "Browser", "Browser": "Browser",
"Broadcast_channel_Description": "Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten", "Broadcast_hint": "Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten",
"Broadcast_Channel": "Broadcast-Kanal",
"Busy": "Beschäftigt", "Busy": "Beschäftigt",
"By_proceeding_you_are_agreeing": "Indem Sie fortfahren, akzeptieren Sie unsere", "By_proceeding_you_are_agreeing": "Indem Sie fortfahren, akzeptieren Sie unsere",
"Cancel_editing": "Bearbeitung abbrechen", "Cancel_editing": "Bearbeitung abbrechen",
@ -394,7 +393,6 @@
"Preferences": "Einstellungen", "Preferences": "Einstellungen",
"Preferences_saved": "Einstellungen gespeichert!", "Preferences_saved": "Einstellungen gespeichert!",
"Privacy_Policy": " Datenschutzbestimmungen", "Privacy_Policy": " Datenschutzbestimmungen",
"Private_Channel": "Privater Kanal",
"Private": "Privat", "Private": "Privat",
"Processing": "Bearbeite …", "Processing": "Bearbeite …",
"Profile_saved_successfully": "Profil erfolgreich gespeichert!", "Profile_saved_successfully": "Profil erfolgreich gespeichert!",
@ -409,7 +407,6 @@
"Reactions": "Reaktionen", "Reactions": "Reaktionen",
"Read_External_Permission_Message": "Rocket.Chat benötigt Zugriff auf Ihre Fotos, Medien und Dateien auf Ihrem Gerät", "Read_External_Permission_Message": "Rocket.Chat benötigt Zugriff auf Ihre Fotos, Medien und Dateien auf Ihrem Gerät",
"Read_External_Permission": "Lese-Zugriff auf Medien", "Read_External_Permission": "Lese-Zugriff auf Medien",
"Read_Only_Channel": "Nur-Lese-Kanal",
"Read_Only": "Schreibgeschützt", "Read_Only": "Schreibgeschützt",
"Read_Receipt": "Lesebestätigung", "Read_Receipt": "Lesebestätigung",
"Receive_Group_Mentions": "Gruppen-Benachrichtigungen erhalten", "Receive_Group_Mentions": "Gruppen-Benachrichtigungen erhalten",
@ -705,9 +702,6 @@
"Team_not_found": "Team nicht gefunden", "Team_not_found": "Team nicht gefunden",
"Create_Team": "Team erstellen", "Create_Team": "Team erstellen",
"Team_Name": "Team-Name", "Team_Name": "Team-Name",
"Private_Team": "Privates Team",
"Read_Only_Team": "Nur-Lesen-Team",
"Broadcast_Team": "Broadcast-Team",
"creating_team": "Team erstellen", "creating_team": "Team erstellen",
"team-name-already-exists": "Ein Team mit diesem Namen existiert bereits", "team-name-already-exists": "Ein Team mit diesem Namen existiert bereits",
"Add_Channel_to_Team": "Kanal zum Team hinzufügen", "Add_Channel_to_Team": "Kanal zum Team hinzufügen",

View File

@ -126,8 +126,6 @@
"Black": "Black", "Black": "Black",
"Block_user": "Block user", "Block_user": "Block user",
"Browser": "Browser", "Browser": "Browser",
"Broadcast_channel_Description": "Only authorized users can write new messages, but the other users will be able to reply",
"Broadcast_Channel": "Broadcast Channel",
"Busy": "Busy", "Busy": "Busy",
"By_proceeding_you_are_agreeing": "By proceeding you are agreeing to our", "By_proceeding_you_are_agreeing": "By proceeding you are agreeing to our",
"Cancel_editing": "Cancel editing", "Cancel_editing": "Cancel editing",
@ -408,7 +406,6 @@
"Preferences": "Preferences", "Preferences": "Preferences",
"Preferences_saved": "Preferences saved!", "Preferences_saved": "Preferences saved!",
"Privacy_Policy": " Privacy Policy", "Privacy_Policy": " Privacy Policy",
"Private_Channel": "Private Channel",
"Private": "Private", "Private": "Private",
"Processing": "Processing...", "Processing": "Processing...",
"Profile_saved_successfully": "Profile saved successfully!", "Profile_saved_successfully": "Profile saved successfully!",
@ -423,7 +420,6 @@
"Reactions": "Reactions", "Reactions": "Reactions",
"Read_External_Permission_Message": "Rocket.Chat needs to access photos, media, and files on your device", "Read_External_Permission_Message": "Rocket.Chat needs to access photos, media, and files on your device",
"Read_External_Permission": "Read Media Permission", "Read_External_Permission": "Read Media Permission",
"Read_Only_Channel": "Read Only Channel",
"Read_Only": "Read Only", "Read_Only": "Read Only",
"Read_Receipt": "Read Receipt", "Read_Receipt": "Read Receipt",
"Receive_Group_Mentions": "Receive Group Mentions", "Receive_Group_Mentions": "Receive Group Mentions",
@ -726,9 +722,6 @@
"Team_not_found": "Team not found", "Team_not_found": "Team not found",
"Create_Team": "Create Team", "Create_Team": "Create Team",
"Team_Name": "Team Name", "Team_Name": "Team Name",
"Private_Team": "Private Team",
"Read_Only_Team": "Read Only Team",
"Broadcast_Team": "Broadcast Team",
"creating_team": "creating team", "creating_team": "creating team",
"team-name-already-exists": "A team with that name already exists", "team-name-already-exists": "A team with that name already exists",
"Add_Channel_to_Team": "Add Channel to Team", "Add_Channel_to_Team": "Add Channel to Team",
@ -844,6 +837,25 @@
"totp-invalid": "Code or password invalid", "totp-invalid": "Code or password invalid",
"Close_Chat": "Close Chat", "Close_Chat": "Close Chat",
"Select_tags": "Select tags", "Select_tags": "Select tags",
"Skip": "Skip",
"N_Selected_members": "{{n}} selected",
"Broadcast": "Broadcast",
"Broadcast_hint": "Only authorized users can write new messages, but the other users will be able to reply",
"Team_hint_private": "Only invited people can join",
"Team_hint_public": "When disabled, anyone can join the team",
"Team_hint_not_read_only": "All users in this team can write messages",
"Team_hint_encrypted": "End to end encrypted team. Search will not work with encrypted Teams and notifications may not show the messages content.",
"Team_hint_encrypted_not_available": "Only available for private team",
"Channel_hint_private":"Only invited users can access this Channel",
"Channel_hint_public":"Everyone can access this channel",
"Channel_hint_encrypted": "End to end encrypted channel. Search will not work with encrypted channels and notifications may not show the messages content.",
"Channel_hint_not_read_only": "All users in the channel can write new messages",
"Channel_hint_encrypted_not_available": "Not available for Public Channels",
"Read_only_hint":"Only authorized users can write new messages",
"Discussion": "Discussion",
"Channel": "Channel",
"Team": "Team",
"Select_Members": "Select Members",
"Also_send_thread_message_to_channel_behavior": "Also send thread message to channel", "Also_send_thread_message_to_channel_behavior": "Also send thread message to channel",
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the Also send to channel behavior" "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the Also send to channel behavior"
} }

View File

@ -108,8 +108,7 @@
"Back": "Volver", "Back": "Volver",
"Black": "Negro", "Black": "Negro",
"Block_user": "Bloquear usuario", "Block_user": "Bloquear usuario",
"Broadcast_channel_Description": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.", "Broadcast_hint": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.",
"Broadcast_Channel": "Canal de Transmisión",
"Busy": "Ocupado", "Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Al proceder estarás de acuerdo", "By_proceeding_you_are_agreeing": "Al proceder estarás de acuerdo",
"Cancel_editing": "Cancelar edición", "Cancel_editing": "Cancelar edición",
@ -273,7 +272,6 @@
"Preferences": "Preferencias", "Preferences": "Preferencias",
"Preferences_saved": "¡Preferencias guardadas!", "Preferences_saved": "¡Preferencias guardadas!",
"Privacy_Policy": "Política de privacidad", "Privacy_Policy": "Política de privacidad",
"Private_Channel": "Canal privado",
"Private": "Privado", "Private": "Privado",
"Processing": "Procesando...", "Processing": "Procesando...",
"Profile_saved_successfully": "¡Perfil guardado correctamente!", "Profile_saved_successfully": "¡Perfil guardado correctamente!",
@ -286,7 +284,6 @@
"Reactions_are_disabled": "Las reacciones están desactivadas", "Reactions_are_disabled": "Las reacciones están desactivadas",
"Reactions_are_enabled": "Las reacciones están activadas", "Reactions_are_enabled": "Las reacciones están activadas",
"Reactions": "Reacciones", "Reactions": "Reacciones",
"Read_Only_Channel": "Canal de sólo lectura",
"Read_Only": "Sólo lectura ", "Read_Only": "Sólo lectura ",
"Read_Receipt": "Comprobante de lectura", "Read_Receipt": "Comprobante de lectura",
"Receive_Group_Mentions": "Recibir menciones de grupo", "Receive_Group_Mentions": "Recibir menciones de grupo",

View File

@ -119,8 +119,7 @@
"Black": "Noir", "Black": "Noir",
"Block_user": "Bloquer l'utilisateur", "Block_user": "Bloquer l'utilisateur",
"Browser": "Navigateur", "Browser": "Navigateur",
"Broadcast_channel_Description": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.", "Broadcast_hint": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.",
"Broadcast_Channel": "Canal de diffusion",
"Busy": "Occupé", "Busy": "Occupé",
"By_proceeding_you_are_agreeing": "En poursuivant, vous acceptez nos", "By_proceeding_you_are_agreeing": "En poursuivant, vous acceptez nos",
"Cancel_editing": "Annuler la modification", "Cancel_editing": "Annuler la modification",
@ -398,7 +397,6 @@
"Preferences": "Préférences", "Preferences": "Préférences",
"Preferences_saved": "Préférences sauvegardées !", "Preferences_saved": "Préférences sauvegardées !",
"Privacy_Policy": " Politique de confidentialité", "Privacy_Policy": " Politique de confidentialité",
"Private_Channel": "Canal privé",
"Private": "Privé", "Private": "Privé",
"Processing": "Traitement...", "Processing": "Traitement...",
"Profile_saved_successfully": "Profil enregistré avec succès !", "Profile_saved_successfully": "Profil enregistré avec succès !",
@ -413,7 +411,6 @@
"Reactions": "Réactions", "Reactions": "Réactions",
"Read_External_Permission_Message": "Rocket.Chat doit accéder aux photos, aux médias et aux fichiers sur votre appareil", "Read_External_Permission_Message": "Rocket.Chat doit accéder aux photos, aux médias et aux fichiers sur votre appareil",
"Read_External_Permission": "Permission de lecture des fichiers", "Read_External_Permission": "Permission de lecture des fichiers",
"Read_Only_Channel": "Canal en lecture seule",
"Read_Only": "Lecture seule", "Read_Only": "Lecture seule",
"Read_Receipt": "Accusé de réception", "Read_Receipt": "Accusé de réception",
"Receive_Group_Mentions": "Recevoir des mentions de groupe", "Receive_Group_Mentions": "Recevoir des mentions de groupe",
@ -715,9 +712,6 @@
"Team_not_found": "Equipe non trouvée", "Team_not_found": "Equipe non trouvée",
"Create_Team": "Créer une équipe", "Create_Team": "Créer une équipe",
"Team_Name": "Nom de l'équipe", "Team_Name": "Nom de l'équipe",
"Private_Team": "Equipe privée",
"Read_Only_Team": "Equipe en lecture seule",
"Broadcast_Team": "Equipe de diffusion",
"creating_team": "création de l'équipe", "creating_team": "création de l'équipe",
"team-name-already-exists": "Une équipe portant ce nom existe déjà", "team-name-already-exists": "Une équipe portant ce nom existe déjà",
"Add_Channel_to_Team": "Ajouter un canal à l'équipe", "Add_Channel_to_Team": "Ajouter un canal à l'équipe",

View File

@ -115,8 +115,7 @@
"Black": "Nero", "Black": "Nero",
"Block_user": "Blocca utente", "Block_user": "Blocca utente",
"Browser": "Browser", "Browser": "Browser",
"Broadcast_channel_Description": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere", "Broadcast_hint": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere",
"Broadcast_Channel": "Canale broadcast",
"Busy": "Occupato", "Busy": "Occupato",
"By_proceeding_you_are_agreeing": "Procedendo accetti i nostri", "By_proceeding_you_are_agreeing": "Procedendo accetti i nostri",
"Cancel_editing": "Annulla modifica", "Cancel_editing": "Annulla modifica",
@ -383,7 +382,6 @@
"Preferences": "Impostazioni", "Preferences": "Impostazioni",
"Preferences_saved": "Impostazioni salvate!", "Preferences_saved": "Impostazioni salvate!",
"Privacy_Policy": " Privacy Policy", "Privacy_Policy": " Privacy Policy",
"Private_Channel": "Canale privato",
"Private": "Privato", "Private": "Privato",
"Processing": "Elaborazione...", "Processing": "Elaborazione...",
"Profile_saved_successfully": "Profilo salvato correttamente!", "Profile_saved_successfully": "Profilo salvato correttamente!",
@ -398,7 +396,6 @@
"Reactions": "Reazioni", "Reactions": "Reazioni",
"Read_External_Permission_Message": "Rocket.Chat deve accedere alle foto, media, e documenti sul tuo dispositivo", "Read_External_Permission_Message": "Rocket.Chat deve accedere alle foto, media, e documenti sul tuo dispositivo",
"Read_External_Permission": "Permesso di Lettura della Memoria", "Read_External_Permission": "Permesso di Lettura della Memoria",
"Read_Only_Channel": "Canale in sola lettura",
"Read_Only": "Sola lettura", "Read_Only": "Sola lettura",
"Read_Receipt": "Conferma di lettura", "Read_Receipt": "Conferma di lettura",
"Receive_Group_Mentions": "Ricevi menzioni di gruppo", "Receive_Group_Mentions": "Ricevi menzioni di gruppo",

View File

@ -119,8 +119,7 @@
"Black": "ブラック", "Black": "ブラック",
"Block_user": "ブロックしたユーザー", "Block_user": "ブロックしたユーザー",
"Browser": "ブラウザ", "Browser": "ブラウザ",
"Broadcast_channel_Description": "許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます", "Broadcast_hint": "許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます",
"Broadcast_Channel": "配信チャンネル",
"Busy": "取り込み中", "Busy": "取り込み中",
"By_proceeding_you_are_agreeing": "続行することにより、私達を承認します", "By_proceeding_you_are_agreeing": "続行することにより、私達を承認します",
"Cancel_editing": "編集をキャンセル", "Cancel_editing": "編集をキャンセル",
@ -371,7 +370,6 @@
"Preferences": "設定", "Preferences": "設定",
"Preferences_saved": "設定が保存されました。", "Preferences_saved": "設定が保存されました。",
"Privacy_Policy": " プライバシーポリシー", "Privacy_Policy": " プライバシーポリシー",
"Private_Channel": "プライベートチャンネル",
"Private": "プライベート", "Private": "プライベート",
"Processing": "処理中...", "Processing": "処理中...",
"Profile_saved_successfully": "プロフィールが保存されました!", "Profile_saved_successfully": "プロフィールが保存されました!",
@ -384,7 +382,6 @@
"Reactions_are_disabled": "リアクションは無効化されています", "Reactions_are_disabled": "リアクションは無効化されています",
"Reactions_are_enabled": "リアクションは有効化されています", "Reactions_are_enabled": "リアクションは有効化されています",
"Reactions": "リアクション", "Reactions": "リアクション",
"Read_Only_Channel": "読み取り専用チャンネル",
"Read_Only": "読み取り専用", "Read_Only": "読み取り専用",
"Read_Receipt": "レシートを見る", "Read_Receipt": "レシートを見る",
"Receive_Group_Mentions": "グループの通知を受け取る", "Receive_Group_Mentions": "グループの通知を受け取る",

View File

@ -119,8 +119,7 @@
"Black": "Zwart", "Black": "Zwart",
"Block_user": "Blokkeer gebruiker", "Block_user": "Blokkeer gebruiker",
"Browser": "Browser", "Browser": "Browser",
"Broadcast_channel_Description": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden", "Broadcast_hint": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden",
"Broadcast_Channel": "Uitzendkanaal",
"Busy": "Bezig", "Busy": "Bezig",
"By_proceeding_you_are_agreeing": "Door verder te gaan ga je akkoord met onze", "By_proceeding_you_are_agreeing": "Door verder te gaan ga je akkoord met onze",
"Cancel_editing": "Bewerken annuleren", "Cancel_editing": "Bewerken annuleren",
@ -398,7 +397,6 @@
"Preferences": "Voorkeuren", "Preferences": "Voorkeuren",
"Preferences_saved": "Voorkeuren opgeslagen!", "Preferences_saved": "Voorkeuren opgeslagen!",
"Privacy_Policy": " Privacybeleid", "Privacy_Policy": " Privacybeleid",
"Private_Channel": "Privékanaal",
"Private": "Privé", "Private": "Privé",
"Processing": "Verwerking...", "Processing": "Verwerking...",
"Profile_saved_successfully": "Profiel succesvol opgeslagen!", "Profile_saved_successfully": "Profiel succesvol opgeslagen!",
@ -413,7 +411,6 @@
"Reactions": "Reacties", "Reactions": "Reacties",
"Read_External_Permission_Message": "Rocket.Chat heeft toegang nodig tot foto's, media en bestanden op je apparaat", "Read_External_Permission_Message": "Rocket.Chat heeft toegang nodig tot foto's, media en bestanden op je apparaat",
"Read_External_Permission": "Lees toestemming voor media", "Read_External_Permission": "Lees toestemming voor media",
"Read_Only_Channel": "Alleen-lezen kanaal",
"Read_Only": "Alleen lezen", "Read_Only": "Alleen lezen",
"Read_Receipt": "Leesbevestiging", "Read_Receipt": "Leesbevestiging",
"Receive_Group_Mentions": "Groepsvermeldingen ontvangen", "Receive_Group_Mentions": "Groepsvermeldingen ontvangen",
@ -715,9 +712,6 @@
"Team_not_found": "Team niet gevonden", "Team_not_found": "Team niet gevonden",
"Create_Team": "Team aanmaken", "Create_Team": "Team aanmaken",
"Team_Name": "Teamnaam", "Team_Name": "Teamnaam",
"Private_Team": "Privé team",
"Read_Only_Team": "Alleen-lezen team",
"Broadcast_Team": "Broadcast team",
"creating_team": "team maken", "creating_team": "team maken",
"team-name-already-exists": "Er bestaat al een team met die naam", "team-name-already-exists": "Er bestaat al een team met die naam",
"Add_Channel_to_Team": "Kanaal toevoegen aan team", "Add_Channel_to_Team": "Kanaal toevoegen aan team",

View File

@ -121,8 +121,6 @@
"Black": "Preto", "Black": "Preto",
"Block_user": "Bloquear usuário", "Block_user": "Bloquear usuário",
"Browser": "Navegador", "Browser": "Navegador",
"Broadcast_channel_Description": "Somente usuários autorizados podem escrever novas mensagens, mas os outros usuários poderão responder",
"Broadcast_Channel": "Canal de Transmissão",
"Busy": "Ocupado", "Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Ao prosseguir você está aceitando", "By_proceeding_you_are_agreeing": "Ao prosseguir você está aceitando",
"Cancel_editing": "Cancelar edição", "Cancel_editing": "Cancelar edição",
@ -384,7 +382,6 @@
"Preferences": "Preferências", "Preferences": "Preferências",
"Preferences_saved": "Preferências salvas!", "Preferences_saved": "Preferências salvas!",
"Privacy_Policy": " Política de Privacidade", "Privacy_Policy": " Política de Privacidade",
"Private_Channel": "Canal Privado",
"Private": "Privado", "Private": "Privado",
"Processing": "Processando...", "Processing": "Processando...",
"Profile_saved_successfully": "Perfil salvo com sucesso!", "Profile_saved_successfully": "Perfil salvo com sucesso!",
@ -399,7 +396,6 @@
"Reactions": "Reações", "Reactions": "Reações",
"Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, mídia e arquivos no seu dispositivo", "Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, mídia e arquivos no seu dispositivo",
"Read_External_Permission": "Permissão de acesso à arquivos", "Read_External_Permission": "Permissão de acesso à arquivos",
"Read_Only_Channel": "Canal Somente Leitura",
"Read_Only": "Somente Leitura", "Read_Only": "Somente Leitura",
"Read_Receipt": "Lida por", "Read_Receipt": "Lida por",
"Receive_Group_Mentions": "Receber menções de grupo", "Receive_Group_Mentions": "Receber menções de grupo",
@ -683,7 +679,6 @@
"Teams": "Times", "Teams": "Times",
"No_team_channels_found": "Nenhum canal encontrado", "No_team_channels_found": "Nenhum canal encontrado",
"Team_not_found": "Time não encontrado", "Team_not_found": "Time não encontrado",
"Private_Team": "Equipe Privada",
"Add_Channel_to_Team": "Adicionar Canal ao Time", "Add_Channel_to_Team": "Adicionar Canal ao Time",
"Left_The_Team_Successfully": "Saiu do time com sucesso", "Left_The_Team_Successfully": "Saiu do time com sucesso",
"Create_New": "Criar", "Create_New": "Criar",
@ -797,6 +792,25 @@
"totp-invalid": "Código ou senha inválida", "totp-invalid": "Código ou senha inválida",
"Close_Chat": "Fechar Conversa", "Close_Chat": "Fechar Conversa",
"Select_tags": "Selecionar tag(s)", "Select_tags": "Selecionar tag(s)",
"Skip": "Pular",
"N_Selected_members": "{{n}} selecionados",
"Broadcast": "Transmissão",
"Broadcast_hint": "Somente usuários autorizados podem escrever novas mensagens, mas os outros usuários poderão responder",
"Team_hint_private": "Apenas pessoas convidadas podem entrar",
"Team_hint_public": "Quando desativado, qualquer um pode entrar na equipe",
"Team_hint_not_read_only": "Todos os usuários nesta equipe podem escrever mensagens",
"Team_hint_encrypted": "Equipe criptografada de ponta a ponta. A pesquisa não funcionará com equipes criptografadas e as notificações poderão não exibir o conteúdo das mensagens.",
"Team_hint_encrypted_not_available": "Disponível apenas para equipes privadas",
"Channel_hint_private":"Apenas usuários convidados podem acessar este canal",
"Channel_hint_public":"Todos podem acessar este canal",
"Channel_hint_encrypted": "Canal criptografado de ponta a ponta. A pesquisa não funcionará com canais criptografados e as notificações podem não mostrar o conteúdo das mensagens.",
"Channel_hint_not_read_only": "Todos usuários no canal podem enviar mensagens novas",
"Channel_hint_encrypted_not_available": "Indisponível para canais públicos",
"Read_only_hint":"Somente usuários autorizados podem escrever novas mensagens",
"Discussion": "Discussão",
"Channel": "Canal",
"Team": "Time",
"Select_Members": "Selecionar Membros",
"Also_send_thread_message_to_channel_behavior": "Também enviar mensagem do tópico para o canal", "Also_send_thread_message_to_channel_behavior": "Também enviar mensagem do tópico para o canal",
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal" "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal"
} }

View File

@ -118,8 +118,7 @@
"Black": "Preto", "Black": "Preto",
"Block_user": "Bloquear utilizador", "Block_user": "Bloquear utilizador",
"Browser": "Navegador", "Browser": "Navegador",
"Broadcast_channel_Description": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder", "Broadcast_hint": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder",
"Broadcast_Channel": "Canal de Transmissão",
"Busy": "Ocupado", "Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Ao prosseguir você concorda com o(s) nosso(s)", "By_proceeding_you_are_agreeing": "Ao prosseguir você concorda com o(s) nosso(s)",
"Cancel_editing": "Cancelar edição", "Cancel_editing": "Cancelar edição",
@ -390,7 +389,6 @@
"Preferences": "Preferências", "Preferences": "Preferências",
"Preferences_saved": "Preferências guardadas!", "Preferences_saved": "Preferências guardadas!",
"Privacy_Policy": " Política de Privacidade", "Privacy_Policy": " Política de Privacidade",
"Private_Channel": "Canal Privado",
"Private": "Privado", "Private": "Privado",
"Processing": "A processar...", "Processing": "A processar...",
"Profile_saved_successfully": "Perfil actualizado com sucesso!", "Profile_saved_successfully": "Perfil actualizado com sucesso!",
@ -405,7 +403,6 @@
"Reactions": "Reacções", "Reactions": "Reacções",
"Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, média e arquivos em seu dispositivo", "Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, média e arquivos em seu dispositivo",
"Read_External_Permission": "Permissão de leitura da média", "Read_External_Permission": "Permissão de leitura da média",
"Read_Only_Channel": "Canal só de leitura",
"Read_Only": "Só de Leitura", "Read_Only": "Só de Leitura",
"Read_Receipt": "Recibos de leitura", "Read_Receipt": "Recibos de leitura",
"Register": "Registar", "Register": "Registar",

View File

@ -119,8 +119,7 @@
"Black": "Черный", "Black": "Черный",
"Block_user": "Блокировать пользователя", "Block_user": "Блокировать пользователя",
"Browser": "Браузер", "Browser": "Браузер",
"Broadcast_channel_Description": "Только авторизованные пользователи могут писать новые сообщения, но другие пользователи смогут ответить", "Broadcast_hint": "Только авторизованные пользователи могут писать новые сообщения, но другие пользователи смогут ответить",
"Broadcast_Channel": "Широковещательный канал",
"Busy": "Занят", "Busy": "Занят",
"By_proceeding_you_are_agreeing": "Продолжая, вы соглашаетесь с нашими", "By_proceeding_you_are_agreeing": "Продолжая, вы соглашаетесь с нашими",
"Cancel_editing": "Отменить правку", "Cancel_editing": "Отменить правку",
@ -394,7 +393,6 @@
"Preferences": "Настройки", "Preferences": "Настройки",
"Preferences_saved": "Настройки сохранены!", "Preferences_saved": "Настройки сохранены!",
"Privacy_Policy": " Политика конфиденциальности", "Privacy_Policy": " Политика конфиденциальности",
"Private_Channel": "Приватный канал",
"Private": "Приватный", "Private": "Приватный",
"Processing": "Обработка...", "Processing": "Обработка...",
"Profile_saved_successfully": "Профиль успешно сохранен!", "Profile_saved_successfully": "Профиль успешно сохранен!",
@ -409,7 +407,6 @@
"Reactions": "Реакции", "Reactions": "Реакции",
"Read_External_Permission_Message": "Rocket.Chat необходим доступ к фотографиям, медиа и другим файлам на вашем устройстве", "Read_External_Permission_Message": "Rocket.Chat необходим доступ к фотографиям, медиа и другим файлам на вашем устройстве",
"Read_External_Permission": "Разрешение на Чтение Медиа", "Read_External_Permission": "Разрешение на Чтение Медиа",
"Read_Only_Channel": "Канал только для чтения",
"Read_Only": "Только для чтения", "Read_Only": "Только для чтения",
"Read_Receipt": "Уведомление о прочтении", "Read_Receipt": "Уведомление о прочтении",
"Receive_Group_Mentions": "Получать групповые уведомления", "Receive_Group_Mentions": "Получать групповые уведомления",
@ -706,9 +703,6 @@
"Team_not_found": "Команда не найдена", "Team_not_found": "Команда не найдена",
"Create_Team": "Создать Команду", "Create_Team": "Создать Команду",
"Team_Name": "Имя Команды", "Team_Name": "Имя Команды",
"Private_Team": "Приватная Команда",
"Read_Only_Team": "Команда только для чтения",
"Broadcast_Team": "Широковещательная Команда",
"creating_team": "создание Команды", "creating_team": "создание Команды",
"team-name-already-exists": "Команда с таким названием уже существует", "team-name-already-exists": "Команда с таким названием уже существует",
"Add_Channel_to_Team": "Добавить канал в Команду", "Add_Channel_to_Team": "Добавить канал в Команду",

View File

@ -115,8 +115,7 @@
"Black": "Koyu", "Black": "Koyu",
"Block_user": "Kullanıcıyı engelle", "Block_user": "Kullanıcıyı engelle",
"Browser": "Tarayıcı", "Browser": "Tarayıcı",
"Broadcast_channel_Description": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir", "Broadcast_hint": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir",
"Broadcast_Channel": "Kanala Yayınla",
"Busy": "Meşgul", "Busy": "Meşgul",
"By_proceeding_you_are_agreeing": "Devam ederek kabul ediyorsunuz: ", "By_proceeding_you_are_agreeing": "Devam ederek kabul ediyorsunuz: ",
"Cancel_editing": "Düzenlemeyi iptal et", "Cancel_editing": "Düzenlemeyi iptal et",
@ -384,7 +383,6 @@
"Preferences": "Tercihler", "Preferences": "Tercihler",
"Preferences_saved": "Tercihler kaydedildi!", "Preferences_saved": "Tercihler kaydedildi!",
"Privacy_Policy": " Privacy Policy", "Privacy_Policy": " Privacy Policy",
"Private_Channel": "Özel Kanal",
"Private": "Özel", "Private": "Özel",
"Processing": "İşleniyor...", "Processing": "İşleniyor...",
"Profile_saved_successfully": "Profil başarıyla kaydedildi!", "Profile_saved_successfully": "Profil başarıyla kaydedildi!",
@ -399,7 +397,6 @@
"Reactions": "Tepkiler", "Reactions": "Tepkiler",
"Read_External_Permission_Message": "Rocket.Chat'in cihazınızdaki fotoğraflara, medyaya ve dosyalara erişmesi gerekiyor", "Read_External_Permission_Message": "Rocket.Chat'in cihazınızdaki fotoğraflara, medyaya ve dosyalara erişmesi gerekiyor",
"Read_External_Permission": "Medya Okuma İzni ", "Read_External_Permission": "Medya Okuma İzni ",
"Read_Only_Channel": "Yazma Kısıtlı Kanal",
"Read_Only": "Yazma Kısıtlı", "Read_Only": "Yazma Kısıtlı",
"Read_Receipt": "Okundu Bilgisi", "Read_Receipt": "Okundu Bilgisi",
"Receive_Group_Mentions": "Grup Bahsetmelerini Al", "Receive_Group_Mentions": "Grup Bahsetmelerini Al",

View File

@ -115,8 +115,7 @@
"Black": "黑色", "Black": "黑色",
"Block_user": "屏蔽此用户", "Block_user": "屏蔽此用户",
"Browser": "浏览器", "Browser": "浏览器",
"Broadcast_channel_Description": "只有经过授权的用户才能写新信息,但其他用户可以回复", "Broadcast_hint": "只有经过授权的用户才能写新信息,但其他用户可以回复",
"Broadcast_Channel": "广播频道",
"Busy": "忙碌", "Busy": "忙碌",
"By_proceeding_you_are_agreeing": "继续操作,请同意我们的", "By_proceeding_you_are_agreeing": "继续操作,请同意我们的",
"Cancel_editing": "取消编辑", "Cancel_editing": "取消编辑",
@ -381,7 +380,6 @@
"Preferences": "偏好设置", "Preferences": "偏好设置",
"Preferences_saved": "偏好已保存!", "Preferences_saved": "偏好已保存!",
"Privacy_Policy": "隐私政策", "Privacy_Policy": "隐私政策",
"Private_Channel": "私人频道",
"Private": "私有的", "Private": "私有的",
"Processing": "处理中", "Processing": "处理中",
"Profile_saved_successfully": "个人资料保存成功!", "Profile_saved_successfully": "个人资料保存成功!",
@ -396,7 +394,6 @@
"Reactions": "表情貼", "Reactions": "表情貼",
"Read_External_Permission_Message": "Rocket.Chat 需要存取您装置上的相片、多媒体及文件", "Read_External_Permission_Message": "Rocket.Chat 需要存取您装置上的相片、多媒体及文件",
"Read_External_Permission": "读取媒体权限", "Read_External_Permission": "读取媒体权限",
"Read_Only_Channel": "只读频道",
"Read_Only": "只读", "Read_Only": "只读",
"Read_Receipt": "查看已读人员", "Read_Receipt": "查看已读人员",
"Receive_Group_Mentions": "接收群组提及", "Receive_Group_Mentions": "接收群组提及",

View File

@ -116,8 +116,7 @@
"Black": "黑色", "Black": "黑色",
"Block_user": "封鎖此用戶", "Block_user": "封鎖此用戶",
"Browser": "瀏覽器", "Browser": "瀏覽器",
"Broadcast_channel_Description": "只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆", "Broadcast_hint": "只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆",
"Broadcast_Channel": "廣播頻道",
"Busy": "忙碌", "Busy": "忙碌",
"By_proceeding_you_are_agreeing": "若要繼續操作,請同意我們的", "By_proceeding_you_are_agreeing": "若要繼續操作,請同意我們的",
"Cancel_editing": "取消編輯", "Cancel_editing": "取消編輯",
@ -383,7 +382,6 @@
"Preferences": "偏好設定", "Preferences": "偏好設定",
"Preferences_saved": "偏好設定已被儲存!", "Preferences_saved": "偏好設定已被儲存!",
"Privacy_Policy": "隱私政策", "Privacy_Policy": "隱私政策",
"Private_Channel": "私人頻道",
"Private": "私有的", "Private": "私有的",
"Processing": "處理中", "Processing": "處理中",
"Profile_saved_successfully": "個人資料儲存成功!", "Profile_saved_successfully": "個人資料儲存成功!",
@ -398,7 +396,6 @@
"Reactions": "表情貼", "Reactions": "表情貼",
"Read_External_Permission_Message": "Rocket.Chat 需要存取您裝置上的相片、多媒體及檔案", "Read_External_Permission_Message": "Rocket.Chat 需要存取您裝置上的相片、多媒體及檔案",
"Read_External_Permission": "讀取媒體權限", "Read_External_Permission": "讀取媒體權限",
"Read_Only_Channel": "唯讀頻道",
"Read_Only": "唯讀", "Read_Only": "唯讀",
"Read_Receipt": "查看已讀人員", "Read_Receipt": "查看已讀人員",
"Receive_Group_Mentions": "接收群組提及", "Receive_Group_Mentions": "接收群組提及",

View File

@ -123,7 +123,7 @@ const ChatsStackNavigator = () => {
options={ThreadMessagesView.navigationOptions} options={ThreadMessagesView.navigationOptions}
/> />
<ChatsStack.Screen name='TeamChannelsView' component={TeamChannelsView} /> <ChatsStack.Screen name='TeamChannelsView' component={TeamChannelsView} />
<ChatsStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} /> <ChatsStack.Screen name='CreateChannelView' component={CreateChannelView} />
<ChatsStack.Screen name='AddChannelTeamView' component={AddChannelTeamView} /> <ChatsStack.Screen name='AddChannelTeamView' component={AddChannelTeamView} />
<ChatsStack.Screen <ChatsStack.Screen
name='AddExistingChannelView' name='AddExistingChannelView'
@ -252,13 +252,9 @@ const NewMessageStackNavigator = () => {
<NewMessageStack.Navigator <NewMessageStack.Navigator
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions} screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}
> >
<NewMessageStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} /> <NewMessageStack.Screen name='NewMessageView' component={NewMessageView} />
<NewMessageStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} /> <NewMessageStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} />
<NewMessageStack.Screen <NewMessageStack.Screen name='CreateChannelView' component={CreateChannelView} />
name='CreateChannelView'
component={CreateChannelView}
options={CreateChannelView.navigationOptions}
/>
<NewMessageStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} /> <NewMessageStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} />
</NewMessageStack.Navigator> </NewMessageStack.Navigator>
); );

View File

@ -187,9 +187,9 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
<ModalStack.Screen name='ProfileView' component={ProfileView} /> <ModalStack.Screen name='ProfileView' component={ProfileView} />
<ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} /> <ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} />
<ModalStack.Screen name='AdminPanelView' component={AdminPanelView} /> <ModalStack.Screen name='AdminPanelView' component={AdminPanelView} />
<ModalStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} /> <ModalStack.Screen name='NewMessageView' component={NewMessageView} />
<ModalStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} /> <ModalStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} />
<ModalStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} /> <ModalStack.Screen name='CreateChannelView' component={CreateChannelView} />
<ModalStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} /> <ModalStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} />
<ModalStack.Screen name='E2ESaveYourPasswordView' component={E2ESaveYourPasswordView} /> <ModalStack.Screen name='E2ESaveYourPasswordView' component={E2ESaveYourPasswordView} />
<ModalStack.Screen name='E2EHowItWorksView' component={E2EHowItWorksView} /> <ModalStack.Screen name='E2EHowItWorksView' component={E2EHowItWorksView} />

View File

@ -1,427 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { FlatList, ScrollView, StyleSheet, Switch, Text, View, SwitchProps } from 'react-native';
import { dequal } from 'dequal';
import * as List from '../containers/List';
import { TextInput } from '../containers/TextInput';
import { sendLoadingEvent } from '../containers/Loading';
import { createChannelRequest } from '../actions/createChannel';
import { removeUser } from '../actions/selectedUsers';
import KeyboardView from '../containers/KeyboardView';
import scrollPersistTaps from '../lib/methods/helpers/scrollPersistTaps';
import I18n from '../i18n';
import UserItem from '../containers/UserItem';
import * as HeaderButton from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
import { SWITCH_TRACK_COLOR, themes } from '../lib/constants';
import { withTheme } from '../theme';
import { Review } from '../lib/methods/helpers/review';
import { getUserSelector } from '../selectors/login';
import { events, logEvent } from '../lib/methods/helpers/log';
import SafeAreaView from '../containers/SafeAreaView';
import sharedStyles from './Styles';
import { ChatsStackParamList } from '../stacks/types';
import { IApplicationState, IBaseScreen, IUser } from '../definitions';
import { hasPermission } from '../lib/methods/helpers';
const styles = StyleSheet.create({
container: {
flex: 1
},
list: {
width: '100%'
},
input: {
height: 54,
paddingHorizontal: 18,
fontSize: 17,
...sharedStyles.textRegular
},
switchContainer: {
height: 54,
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: 'row',
paddingHorizontal: 18
},
label: {
fontSize: 17,
...sharedStyles.textMedium
},
invitedHeader: {
marginTop: 18,
marginHorizontal: 15,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
invitedTitle: {
fontSize: 18,
...sharedStyles.textSemibold,
lineHeight: 41
},
invitedCount: {
fontSize: 14,
...sharedStyles.textRegular
}
});
interface IOtherUser {
_id: string;
name: string;
fname: string;
}
interface ICreateChannelViewState {
channelName: string;
type: boolean;
readOnly: boolean;
encrypted: boolean;
broadcast: boolean;
isTeam: boolean;
permissions: boolean[];
}
interface ICreateChannelViewProps extends IBaseScreen<ChatsStackParamList, 'CreateChannelView'> {
baseUrl: string;
error: object;
failure: boolean;
isFetching: boolean;
encryptionEnabled: boolean;
users: IOtherUser[];
user: IUser;
teamId: string;
createPublicChannelPermission: string[] | undefined;
createPrivateChannelPermission: string[] | undefined;
}
interface ISwitch extends SwitchProps {
id: string;
label: string;
}
class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreateChannelViewState> {
private teamId?: string;
constructor(props: ICreateChannelViewProps) {
super(props);
const { route } = this.props;
const isTeam = route?.params?.isTeam || false;
this.teamId = route?.params?.teamId;
this.state = {
channelName: '',
type: true,
readOnly: false,
encrypted: false,
broadcast: false,
isTeam,
permissions: []
};
this.setHeader();
}
componentDidMount() {
this.handleHasPermission();
}
shouldComponentUpdate(nextProps: ICreateChannelViewProps, nextState: ICreateChannelViewState) {
const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state;
const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } =
this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextState.channelName !== channelName) {
return true;
}
if (nextState.type !== type) {
return true;
}
if (nextState.readOnly !== readOnly) {
return true;
}
if (nextState.encrypted !== encrypted) {
return true;
}
if (nextState.broadcast !== broadcast) {
return true;
}
if (nextState.permissions !== permissions) {
return true;
}
if (nextProps.isFetching !== isFetching) {
return true;
}
if (nextProps.encryptionEnabled !== encryptionEnabled) {
return true;
}
if (!dequal(nextProps.createPublicChannelPermission, createPublicChannelPermission)) {
return true;
}
if (!dequal(nextProps.createPrivateChannelPermission, createPrivateChannelPermission)) {
return true;
}
if (!dequal(nextProps.users, users)) {
return true;
}
return false;
}
componentDidUpdate(prevProps: ICreateChannelViewProps) {
const { createPublicChannelPermission, createPrivateChannelPermission, isFetching } = this.props;
if (
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
!dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission)
) {
this.handleHasPermission();
}
if (isFetching !== prevProps.isFetching) {
sendLoadingEvent({ visible: isFetching });
}
}
setHeader = () => {
const { navigation } = this.props;
const { isTeam } = this.state;
navigation.setOptions({
title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')
});
};
toggleRightButton = (channelName: string) => {
const { navigation } = this.props;
navigation.setOptions({
headerRight: () =>
channelName.trim().length > 0 && (
<HeaderButton.Container>
<HeaderButton.Item title={I18n.t('Create')} onPress={this.submit} testID='create-channel-submit' />
</HeaderButton.Container>
)
});
};
onChangeText = (channelName: string) => {
this.toggleRightButton(channelName);
this.setState({ channelName });
};
submit = () => {
const { channelName, type, readOnly, broadcast, encrypted, isTeam } = this.state;
const { users: usersProps, isFetching, dispatch } = this.props;
if (!channelName.trim() || isFetching) {
return;
}
// transform users object into array of usernames
const users = usersProps.map(user => user.name);
// create channel or team
const data = {
name: channelName,
users,
type,
readOnly,
broadcast,
encrypted,
isTeam,
teamId: this.teamId
};
dispatch(createChannelRequest(data));
Review.pushPositiveEvent();
};
removeUser = (user: IOtherUser) => {
logEvent(events.CR_REMOVE_USER);
const { dispatch } = this.props;
dispatch(removeUser(user));
};
renderSwitch = ({ id, value, label, onValueChange, disabled = false }: ISwitch) => {
const { theme } = this.props;
return (
<View style={[styles.switchContainer, { backgroundColor: themes[theme].backgroundColor }]}>
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t(label)}</Text>
<Switch
value={value}
onValueChange={onValueChange}
testID={`create-channel-${id}`}
trackColor={SWITCH_TRACK_COLOR}
disabled={disabled}
/>
</View>
);
};
handleHasPermission = async () => {
const { createPublicChannelPermission, createPrivateChannelPermission } = this.props;
const permissions = [createPublicChannelPermission, createPrivateChannelPermission];
const permissionsToCreate = await hasPermission(permissions);
this.setState({ permissions: permissionsToCreate });
};
renderType() {
const { type, isTeam, permissions } = this.state;
const isDisabled = permissions.filter(r => r === true).length <= 1;
return this.renderSwitch({
id: 'type',
value: permissions[1] ? type : false,
disabled: isDisabled,
label: isTeam ? 'Private_Team' : 'Private_Channel',
onValueChange: (value: boolean) => {
logEvent(events.CR_TOGGLE_TYPE);
// If we set the channel as public, encrypted status should be false
this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted }));
}
});
}
renderReadOnly() {
const { readOnly, broadcast, isTeam } = this.state;
return this.renderSwitch({
id: 'readonly',
value: readOnly,
label: isTeam ? 'Read_Only_Team' : 'Read_Only_Channel',
onValueChange: value => {
logEvent(events.CR_TOGGLE_READ_ONLY);
this.setState({ readOnly: value });
},
disabled: broadcast
});
}
renderEncrypted() {
const { type, encrypted } = this.state;
const { encryptionEnabled } = this.props;
if (!encryptionEnabled) {
return null;
}
return this.renderSwitch({
id: 'encrypted',
value: encrypted,
label: 'Encrypted',
onValueChange: value => {
logEvent(events.CR_TOGGLE_ENCRYPTED);
this.setState({ encrypted: value });
},
disabled: !type
});
}
renderBroadcast() {
const { broadcast, readOnly, isTeam } = this.state;
return this.renderSwitch({
id: 'broadcast',
value: broadcast,
label: isTeam ? 'Broadcast_Team' : 'Broadcast_Channel',
onValueChange: value => {
logEvent(events.CR_TOGGLE_BROADCAST);
this.setState({
broadcast: value,
readOnly: value ? true : readOnly
});
}
});
}
renderItem = ({ item }: { item: IOtherUser }) => (
<UserItem
name={item.fname}
username={item.name}
onPress={() => this.removeUser(item)}
testID={`create-channel-view-item-${item.name}`}
icon='check'
/>
);
renderInvitedList = () => {
const { users, theme } = this.props;
return (
<FlatList
data={users}
extraData={users}
keyExtractor={item => item._id}
style={[
styles.list,
sharedStyles.separatorVertical,
{
backgroundColor: themes[theme].focusedBackground,
borderColor: themes[theme].separatorColor
}
]}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
keyboardShouldPersistTaps='always'
/>
);
};
render() {
const { channelName, isTeam } = this.state;
const { users, theme } = this.props;
const userCount = users.length;
return (
<KeyboardView
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
contentContainerStyle={[sharedStyles.container, styles.container]}
keyboardVerticalOffset={128}
>
<StatusBar />
<SafeAreaView testID='create-channel-view'>
<ScrollView {...scrollPersistTaps}>
<View style={[sharedStyles.separatorVertical, { borderColor: themes[theme].separatorColor }]}>
<TextInput
autoFocus
style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]}
value={channelName}
onChangeText={this.onChangeText}
placeholder={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
returnKeyType='done'
testID='create-channel-name'
autoCorrect={false}
autoCapitalize='none'
underlineColorAndroid='transparent'
/>
<List.Separator />
{this.renderType()}
<List.Separator />
{this.renderReadOnly()}
<List.Separator />
{this.renderEncrypted()}
<List.Separator />
{this.renderBroadcast()}
</View>
<View style={styles.invitedHeader}>
<Text style={[styles.invitedTitle, { color: themes[theme].titleText }]}>{I18n.t('Invite')}</Text>
<Text style={[styles.invitedCount, { color: themes[theme].auxiliaryText }]}>
{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}
</Text>
</View>
{this.renderInvitedList()}
</ScrollView>
</SafeAreaView>
</KeyboardView>
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
baseUrl: state.server.server,
isFetching: state.createChannel.isFetching,
encryptionEnabled: state.encryption.enabled,
users: state.selectedUsers.users,
user: getUserSelector(state),
createPublicChannelPermission: state.permissions['create-c'],
createPrivateChannelPermission: state.permissions['create-p']
});
export default connect(mapStateToProps)(withTheme(CreateChannelView));

View File

@ -0,0 +1,40 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { SwitchItem } from './SwitchItem';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'flex-start',
padding: 16
}
});
export default {
title: 'SwitchItem'
};
const testSwitch = {
id: 'switch-id',
hint: 'Read_only_hint',
label: 'Onboarding_title',
onValueChange: () => {},
value: false,
testSwitchID: 'create-channel-switch-id',
testLabelID: 'create-channel-switch-id-hint'
};
export const Switch = () => (
<>
<View style={styles.container}>
<SwitchItem
hint={testSwitch.hint}
id={testSwitch.id}
label={testSwitch.label}
onValueChange={() => testSwitch.onValueChange()}
value={testSwitch.value}
/>
</View>
</>
);

View File

@ -0,0 +1,68 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import i18n from '../../../i18n';
import { SwitchItem, ISwitch } from './SwitchItem';
import { mockedStore as store } from '../../../reducers/mockedStore';
const onPressMock = jest.fn((value: boolean) => value);
const testSwitch = {
id: 'switch-id',
hint: 'Read_only_hint',
label: 'Onboarding_title',
onValueChange: onPressMock,
value: false,
testSwitchID: 'create-channel-switch-id',
testLabelID: 'create-channel-switch-id-hint'
};
const Render = ({ hint, id, label, onValueChange, value }: ISwitch) => (
<Provider store={store}>
<SwitchItem hint={hint} id={id} label={label} onValueChange={onValueChange} value={value} />
</Provider>
);
describe('SwitchItemEncrypted', () => {
it('should not render the Encrypted Switch component', async () => {
const { findByTestId } = render(
<Render
hint={testSwitch.hint}
id={testSwitch.id}
label={testSwitch.label}
onValueChange={value => testSwitch.onValueChange(value)}
value={testSwitch.value}
/>
);
const component = await findByTestId(testSwitch.testSwitchID);
expect(component).toBeTruthy();
});
it('should change value of switch', async () => {
const { findByTestId } = render(
<Render
hint={testSwitch.hint}
id={testSwitch.id}
label={testSwitch.label}
onValueChange={value => testSwitch.onValueChange(value)}
value={testSwitch.value}
/>
);
const component = await findByTestId(testSwitch.testSwitchID);
fireEvent(component, 'valueChange', { value: true });
expect(onPressMock).toHaveReturnedWith({ value: !testSwitch.value });
});
it('check if hint exists and is the same from testSwitch object', async () => {
const { findByTestId } = render(
<Render
hint={testSwitch.hint}
id={testSwitch.id}
label={testSwitch.label}
onValueChange={value => testSwitch.onValueChange(value)}
value={testSwitch.value}
/>
);
const component = await findByTestId(testSwitch.testLabelID);
expect(component.props.children).toBe(i18n.t(testSwitch.hint));
});
});

View File

@ -0,0 +1,59 @@
import React from 'react';
import { StyleSheet, Switch, Text, View, SwitchProps } from 'react-native';
import I18n from '../../../i18n';
import { SWITCH_TRACK_COLOR } from '../../../lib/constants';
import { useTheme } from '../../../theme';
import sharedStyles from '../../Styles';
const styles = StyleSheet.create({
switchContainer: {
minHeight: 54,
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: 'row',
maxHeight: 80,
marginBottom: 12
},
switchTextContainer: {
flex: 1,
marginRight: 8
},
label: {
fontSize: 14,
...sharedStyles.textMedium
},
hint: {
fontSize: 14,
...sharedStyles.textRegular
}
});
export interface ISwitch extends SwitchProps {
id: string;
label: string;
hint: string;
onValueChange: (value: boolean) => void;
}
export const SwitchItem = ({ id, value, label, hint, onValueChange, disabled = false }: ISwitch) => {
const { colors } = useTheme();
return (
<View style={[styles.switchContainer, { backgroundColor: colors.backgroundColor }]}>
<View style={styles.switchTextContainer}>
<Text style={[styles.label, { color: colors.titleText }]}>{I18n.t(label)}</Text>
<Text testID={`create-channel-${id}-hint`} style={[styles.hint, { color: colors.auxiliaryText }]}>
{I18n.t(hint)}
</Text>
</View>
<Switch
value={value}
onValueChange={onValueChange}
testID={`create-channel-${id}`}
trackColor={SWITCH_TRACK_COLOR}
disabled={disabled}
/>
</View>
);
};

View File

@ -0,0 +1,108 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import { SwitchItemEncrypted, ISwitchItemEncrypted } from './SwitchItemEncrypted';
import { mockedStore as store } from '../../../reducers/mockedStore';
import i18n from '../../../i18n';
const onPressMock = jest.fn((value: boolean) => value);
const testEncrypted = {
encrypted: false,
encryptionEnabled: false,
isTeam: false,
onValueChangeEncrypted: onPressMock,
type: false,
testSwitchID: 'create-channel-encrypted',
testLabelID: `create-channel-encrypted-hint`
};
const Render = ({ encrypted, encryptionEnabled, isTeam, onValueChangeEncrypted, type }: ISwitchItemEncrypted) => (
<Provider store={store}>
<SwitchItemEncrypted
encrypted={encrypted}
encryptionEnabled={encryptionEnabled}
isTeam={isTeam}
onValueChangeEncrypted={onValueChangeEncrypted}
type={type}
/>
</Provider>
);
describe('SwitchItemEncrypted', () => {
it('should not render the Encrypted Switch component', async () => {
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
try {
await findByTestId(testEncrypted.testSwitchID);
} catch (e) {
expect(e).toBeTruthy();
}
});
it('should render the Encrypted Switch component', async () => {
testEncrypted.encryptionEnabled = true;
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
const component = await findByTestId(testEncrypted.testSwitchID);
expect(component).toBeTruthy();
});
it('should change value of switch', async () => {
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
const component = await findByTestId(testEncrypted.testSwitchID);
fireEvent(component, 'valueChange', { value: true });
expect(onPressMock).toHaveReturnedWith({ value: !testEncrypted.encrypted });
});
it('label when encrypted and isTeam are false and is a public channel', async () => {
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
const component = await findByTestId(testEncrypted.testLabelID);
expect(component.props.children).toBe(i18n.t('Channel_hint_encrypted_not_available'));
});
it('label when encrypted and isTeam are true and is a private team', async () => {
testEncrypted.isTeam = true;
testEncrypted.type = true;
testEncrypted.encrypted = true;
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
const component = await findByTestId(testEncrypted.testLabelID);
expect(component.props.children).toBe(i18n.t('Team_hint_encrypted'));
});
});

View File

@ -0,0 +1,48 @@
import React from 'react';
import { SwitchItem } from './SwitchItem';
export interface ISwitchItemEncrypted {
encryptionEnabled: boolean;
isTeam: boolean;
type: boolean;
encrypted: boolean;
onValueChangeEncrypted: (value: boolean) => void;
}
export const SwitchItemEncrypted = ({
encryptionEnabled,
isTeam,
type,
encrypted,
onValueChangeEncrypted
}: ISwitchItemEncrypted) => {
if (!encryptionEnabled) {
return null;
}
let hint = '';
if (isTeam && type) {
hint = 'Team_hint_encrypted';
}
if (isTeam && !type) {
hint = 'Team_hint_encrypted_not_available';
}
if (!isTeam && type) {
hint = 'Channel_hint_encrypted';
}
if (!isTeam && !type) {
hint = 'Channel_hint_encrypted_not_available';
}
return (
<SwitchItem
id={'encrypted'}
value={encrypted}
label={'Encrypted'}
hint={hint}
onValueChange={onValueChangeEncrypted}
disabled={!type}
/>
);
};

View File

@ -0,0 +1,37 @@
import React from 'react';
import { SwitchItem } from './SwitchItem';
export const SwitchItemReadOnly = ({
readOnly,
isTeam,
onValueChangeReadOnly,
broadcast
}: {
readOnly: boolean;
isTeam: boolean;
onValueChangeReadOnly: (value: boolean) => void;
broadcast: boolean;
}) => {
let hint = '';
if (readOnly) {
hint = 'Read_only_hint';
}
if (isTeam && !readOnly) {
hint = 'Team_hint_not_read_only';
}
if (!isTeam && !readOnly) {
hint = 'Channel_hint_not_read_only';
}
return (
<SwitchItem
id={'readonly'}
value={readOnly}
label={'Read_Only'}
hint={hint}
onValueChange={onValueChangeReadOnly}
disabled={broadcast}
/>
);
};

View File

@ -0,0 +1,43 @@
import React from 'react';
import { usePermissions } from '../../../lib/hooks';
import { SwitchItem } from './SwitchItem';
export const SwitchItemType = ({
isTeam,
type,
onValueChangeType
}: {
isTeam: boolean;
type: boolean;
onValueChangeType: (value: boolean) => void;
}) => {
const [createChannelPermission, createPrivateChannelPermission] = usePermissions(['create-c', 'create-p']);
const isDisabled = [createChannelPermission, createPrivateChannelPermission].filter(r => r === true).length <= 1;
let hint = '';
if (isTeam && type) {
hint = 'Team_hint_private';
}
if (isTeam && !type) {
hint = 'Team_hint_public';
}
if (!isTeam && type) {
hint = 'Channel_hint_private';
}
if (!isTeam && !type) {
hint = 'Channel_hint_public';
}
return (
<SwitchItem
id={'type'}
value={createPrivateChannelPermission ? type : false}
disabled={isDisabled}
label={'Private'}
hint={hint}
onValueChange={onValueChangeType}
/>
);
};

View File

@ -0,0 +1,78 @@
import React, { useCallback, useState } from 'react';
import { UseFormSetValue } from 'react-hook-form';
import { useAppSelector } from '../../../lib/hooks';
import { events, logEvent } from '../../../lib/methods/helpers/log';
import { SwitchItem } from './SwitchItem';
import { SwitchItemType } from './SwitchItemType';
import { SwitchItemReadOnly } from './SwitchItemReadOnly';
import { SwitchItemEncrypted } from './SwitchItemEncrypted';
import { IFormData } from '..';
export const RoomSettings = ({ isTeam, setValue }: { isTeam: boolean; setValue: UseFormSetValue<IFormData> }) => {
const [type, setType] = useState(true);
const [readOnly, setReadOnly] = useState(false);
const [encrypted, setEncrypted] = useState(false);
const [broadcast, setBroadcast] = useState(false);
const { encryptionEnabled } = useAppSelector(state => ({
encryptionEnabled: state.encryption.enabled
}));
const onValueChangeType = useCallback(
(value: boolean) => {
logEvent(events.CR_TOGGLE_TYPE);
// If we set the channel as public, encrypted status should be false
setType(value);
setValue('type', value);
setEncrypted(value && encrypted);
setValue('encrypted', value && encrypted);
},
[encrypted]
);
const onValueChangeReadOnly = useCallback((value: boolean) => {
logEvent(events.CR_TOGGLE_READ_ONLY);
setReadOnly(value);
setValue('readOnly', value);
}, []);
const onValueChangeEncrypted = useCallback((value: boolean) => {
logEvent(events.CR_TOGGLE_ENCRYPTED);
setEncrypted(value);
setValue('encrypted', value);
}, []);
const onValueChangeBroadcast = (value: boolean) => {
logEvent(events.CR_TOGGLE_BROADCAST);
setBroadcast(value);
setValue('broadcast', value);
setReadOnly(value ? true : readOnly);
setValue('readOnly', value ? true : readOnly);
};
return (
<>
<SwitchItemType isTeam={isTeam} type={type} onValueChangeType={onValueChangeType} />
<SwitchItemReadOnly
broadcast={broadcast}
isTeam={isTeam}
readOnly={readOnly}
onValueChangeReadOnly={onValueChangeReadOnly}
/>
<SwitchItemEncrypted
encryptionEnabled={encryptionEnabled}
isTeam={isTeam}
type={type}
encrypted={encrypted}
onValueChangeEncrypted={onValueChangeEncrypted}
/>
<SwitchItem
id={'broadcast'}
value={broadcast}
label={'Broadcast'}
hint={'Broadcast_hint'}
onValueChange={onValueChangeBroadcast}
/>
</>
);
};

View File

@ -0,0 +1,205 @@
import React, { useCallback, useEffect, useLayoutEffect } from 'react';
import { shallowEqual, useDispatch } from 'react-redux';
import { FlatList, ScrollView, StyleSheet, Text, View } from 'react-native';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { useForm } from 'react-hook-form';
import { useAppSelector } from '../../lib/hooks';
import { sendLoadingEvent } from '../../containers/Loading';
import { createChannelRequest } from '../../actions/createChannel';
import { removeUser as removeUserAction } from '../../actions/selectedUsers';
import KeyboardView from '../../containers/KeyboardView';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar';
import { useTheme } from '../../theme';
import { Review } from '../../lib/methods/helpers/review';
import SafeAreaView from '../../containers/SafeAreaView';
import sharedStyles from '../Styles';
import { ChatsStackParamList } from '../../stacks/types';
import Button from '../../containers/Button';
import { ControlledFormTextInput } from '../../containers/TextInput';
import Chip from '../../containers/Chip';
import { RoomSettings } from './RoomSettings';
import { ISelectedUser } from '../../reducers/selectedUsers';
const styles = StyleSheet.create({
container: {
flex: 1
},
containerTextInput: {
paddingHorizontal: 16,
marginTop: 16
},
containerStyle: {
marginBottom: 28
},
list: {
width: '100%'
},
invitedHeader: {
marginVertical: 12,
marginHorizontal: 16,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
invitedCount: {
fontSize: 12,
...sharedStyles.textRegular
},
invitedList: {
paddingHorizontal: 16
},
buttonCreate: {
marginHorizontal: 16,
marginTop: 24
}
});
export interface IFormData {
channelName: string;
type: boolean;
readOnly: boolean;
encrypted: boolean;
broadcast: boolean;
}
const CreateChannelView = () => {
const {
control,
handleSubmit,
formState: { isDirty },
setValue
} = useForm<IFormData>({
defaultValues: { channelName: '', broadcast: false, encrypted: false, readOnly: false, type: false }
});
const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'CreateChannelView'>>();
const { params } = useRoute<RouteProp<ChatsStackParamList, 'CreateChannelView'>>();
const isTeam = params?.isTeam || false;
const teamId = params?.teamId;
const { colors } = useTheme();
const dispatch = useDispatch();
const { isFetching, useRealName, users } = useAppSelector(
state => ({
isFetching: state.createChannel.isFetching,
users: state.selectedUsers.users,
useRealName: state.settings.UI_Use_Real_Name as boolean
}),
shallowEqual
);
useEffect(() => {
sendLoadingEvent({ visible: isFetching });
}, [isFetching]);
useLayoutEffect(() => {
navigation.setOptions({
title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')
});
}, [isTeam, navigation]);
const removeUser = useCallback(
(user: ISelectedUser) => {
dispatch(removeUserAction(user));
},
[dispatch]
);
const submit = ({ channelName, broadcast, encrypted, readOnly, type }: IFormData) => {
if (!channelName.trim() || isFetching) {
return;
}
// transform users object into array of usernames
const usersMapped = users.map(user => user.name);
// create channel or team
const data = {
name: channelName,
users: usersMapped,
type,
readOnly,
broadcast,
encrypted,
isTeam,
teamId
};
dispatch(createChannelRequest(data));
Review.pushPositiveEvent();
};
return (
<KeyboardView
style={{ backgroundColor: colors.backgroundColor }}
contentContainerStyle={[sharedStyles.container, styles.container]}
keyboardVerticalOffset={128}
>
<StatusBar />
<SafeAreaView style={{ backgroundColor: colors.backgroundColor }} testID='create-channel-view'>
<ScrollView {...scrollPersistTaps}>
<View style={[styles.containerTextInput, { borderColor: colors.separatorColor }]}>
<ControlledFormTextInput
label={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
testID='create-channel-name'
returnKeyType='done'
containerStyle={styles.containerStyle}
name={'channelName'}
control={control}
/>
<RoomSettings isTeam={isTeam} setValue={setValue} />
</View>
{users.length > 0 ? (
<>
<View style={styles.invitedHeader}>
<Text style={[styles.invitedCount, { color: colors.auxiliaryText }]}>
{I18n.t('N_Selected_members', { n: users.length })}
</Text>
</View>
<FlatList
data={users}
extraData={users}
keyExtractor={item => item._id}
style={[
styles.list,
{
backgroundColor: colors.backgroundColor,
borderColor: colors.separatorColor
}
]}
contentContainerStyle={styles.invitedList}
renderItem={({ item }) => {
const name = useRealName && item.fname ? item.fname : item.name;
const username = item.name;
return (
<Chip
text={name}
avatar={username}
onPress={() => removeUser(item)}
testID={`create-channel-view-item-${item.name}`}
/>
);
}}
keyboardShouldPersistTaps='always'
horizontal
/>
</>
) : null}
<Button
title={isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')}
type='primary'
onPress={handleSubmit(submit)}
disabled={!isDirty}
testID='create-channel-submit'
loading={isFetching}
style={styles.buttonCreate}
/>
</ScrollView>
</SafeAreaView>
</KeyboardView>
);
};
export default CreateChannelView;

View File

@ -1,335 +0,0 @@
import { Q } from '@nozbe/watermelondb';
import { StackNavigationOptions } from '@react-navigation/stack';
import { dequal } from 'dequal';
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { createChannelRequest } from '../actions/createChannel';
import { themes } from '../lib/constants';
import * as HeaderButton from '../containers/HeaderButton';
import * as List from '../containers/List';
import SafeAreaView from '../containers/SafeAreaView';
import SearchBox from '../containers/SearchBox';
import StatusBar from '../containers/StatusBar';
import { IApplicationState, IBaseScreen, ISearch, TSubscriptionModel } from '../definitions';
import I18n from '../i18n';
import database from '../lib/database';
import { CustomIcon, TIconsName } from '../containers/CustomIcon';
import Navigation from '../lib/navigation/appNavigation';
import UserItem from '../containers/UserItem';
import { withTheme } from '../theme';
import { goRoom, TGoRoomItem } from '../lib/methods/helpers/goRoom';
import log, { events, logEvent } from '../lib/methods/helpers/log';
import Touch from '../containers/Touch';
import sharedStyles from './Styles';
import { NewMessageStackParamList } from '../stacks/types';
import { search } from '../lib/methods';
import { hasPermission, compareServerVersion } from '../lib/methods/helpers';
const QUERY_SIZE = 50;
const styles = StyleSheet.create({
button: {
height: 46,
flexDirection: 'row',
alignItems: 'center'
},
buttonIcon: {
marginLeft: 18,
marginRight: 16
},
buttonText: {
fontSize: 17,
...sharedStyles.textRegular
},
buttonContainer: {
paddingBottom: 16
}
});
interface IButton {
onPress: () => void;
testID: string;
title: string;
icon: TIconsName;
first?: boolean;
}
interface INewMessageViewState {
search: (ISearch | TSubscriptionModel)[];
chats: TSubscriptionModel[];
permissions: boolean[];
}
interface INewMessageViewProps extends IBaseScreen<NewMessageStackParamList, 'NewMessageView'> {
maxUsers: number;
isMasterDetail: boolean;
serverVersion: string;
createTeamPermission?: string[];
createDirectMessagePermission?: string[];
createPublicChannelPermission?: string[];
createPrivateChannelPermission?: string[];
createDiscussionPermission?: string[];
}
class NewMessageView extends React.Component<INewMessageViewProps, INewMessageViewState> {
static navigationOptions = ({ navigation }: INewMessageViewProps): StackNavigationOptions => ({
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />,
title: I18n.t('New_Message')
});
constructor(props: INewMessageViewProps) {
super(props);
this.init();
this.state = {
search: [],
chats: [],
permissions: []
};
}
// eslint-disable-next-line react/sort-comp
init = async () => {
try {
const db = database.active;
const chats = await db
.get('subscriptions')
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
.fetch();
this.setState({ chats });
} catch (e) {
log(e);
}
};
componentDidMount() {
this.handleHasPermission();
}
componentDidUpdate(prevProps: INewMessageViewProps) {
const {
createTeamPermission,
createPublicChannelPermission,
createPrivateChannelPermission,
createDirectMessagePermission,
createDiscussionPermission
} = this.props;
if (
!dequal(createTeamPermission, prevProps.createTeamPermission) ||
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
!dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) ||
!dequal(createDirectMessagePermission, prevProps.createDirectMessagePermission) ||
!dequal(createDiscussionPermission, prevProps.createDiscussionPermission)
) {
this.handleHasPermission();
}
}
handleSearch = async (text: string) => {
const result = (await search({ text, filterRooms: false })) as ISearch[];
this.setState({
search: result
});
};
onSearchChangeText(text: string) {
this.handleSearch(text);
}
dismiss = () => {
const { navigation } = this.props;
return navigation.pop();
};
createChannel = () => {
logEvent(events.NEW_MSG_CREATE_CHANNEL);
const { navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
};
createTeam = () => {
logEvent(events.NEW_MSG_CREATE_TEAM);
const { navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true })
});
};
createGroupChat = () => {
logEvent(events.NEW_MSG_CREATE_GROUP_CHAT);
const { dispatch, maxUsers, navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => dispatch(createChannelRequest({ group: true })),
buttonText: I18n.t('Create'),
maxUsers
});
};
goRoom = (item: TGoRoomItem) => {
logEvent(events.NEW_MSG_CHAT_WITH_USER);
const { isMasterDetail, navigation } = this.props;
if (isMasterDetail) {
navigation.pop();
}
goRoom({ item, isMasterDetail });
};
renderButton = ({ onPress, testID, title, icon, first }: IButton) => {
const { theme } = this.props;
return (
<Touch onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }} testID={testID}>
<View
style={[
first ? sharedStyles.separatorVertical : sharedStyles.separatorBottom,
styles.button,
{ borderColor: themes[theme].separatorColor }
]}
>
<CustomIcon name={icon} size={24} color={themes[theme].tintColor} style={styles.buttonIcon} />
<Text style={[styles.buttonText, { color: themes[theme].tintColor }]}>{title}</Text>
</View>
</Touch>
);
};
createDiscussion = () => {
logEvent(events.NEW_MSG_CREATE_DISCUSSION);
Navigation.navigate('CreateDiscussionView');
};
handleHasPermission = async () => {
const {
createTeamPermission,
createDirectMessagePermission,
createPublicChannelPermission,
createPrivateChannelPermission,
createDiscussionPermission
} = this.props;
const permissions = [
createPublicChannelPermission,
createPrivateChannelPermission,
createTeamPermission,
createDirectMessagePermission,
createDiscussionPermission
];
const permissionsToCreate = await hasPermission(permissions);
this.setState({ permissions: permissionsToCreate });
};
renderHeader = () => {
const { maxUsers, theme, serverVersion } = this.props;
const { permissions } = this.state;
return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='new-message-view-search' />
<View style={styles.buttonContainer}>
{permissions[0] || permissions[1]
? this.renderButton({
onPress: this.createChannel,
title: I18n.t('Create_Channel'),
icon: 'channel-public',
testID: 'new-message-view-create-channel',
first: true
})
: null}
{compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && permissions[2]
? this.renderButton({
onPress: this.createTeam,
title: I18n.t('Create_Team'),
icon: 'teams',
testID: 'new-message-view-create-team'
})
: null}
{maxUsers > 2 && permissions[3]
? this.renderButton({
onPress: this.createGroupChat,
title: I18n.t('Create_Direct_Messages'),
icon: 'message',
testID: 'new-message-view-create-direct-message'
})
: null}
{permissions[4]
? this.renderButton({
onPress: this.createDiscussion,
title: I18n.t('Create_Discussion'),
icon: 'discussions',
testID: 'new-message-view-create-discussion'
})
: null}
</View>
</View>
);
};
renderItem = ({ item, index }: { item: ISearch | TSubscriptionModel; index: number }) => {
const { search, chats } = this.state;
const { theme } = this.props;
let style = { borderColor: themes[theme].separatorColor };
if (index === 0) {
style = { ...style, ...sharedStyles.separatorTop };
}
if (search.length > 0 && index === search.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
if (search.length === 0 && index === chats.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
const itemSearch = item as ISearch;
const itemModel = item as TSubscriptionModel;
return (
<UserItem
name={itemSearch.search ? itemSearch.name : itemModel.fname || ''}
username={itemSearch.search ? itemSearch.username : itemModel.name}
onPress={() => this.goRoom(itemModel)}
testID={`new-message-view-item-${item.name}`}
style={style}
/>
);
};
renderList = () => {
const { search, chats } = this.state;
const { theme } = this.props;
return (
<FlatList
data={search.length > 0 ? search : chats}
extraData={this.state}
keyExtractor={item => item._id || item.rid}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>
);
};
render() {
return (
<SafeAreaView testID='new-message-view'>
<StatusBar />
{this.renderList()}
</SafeAreaView>
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
serverVersion: state.server.version as string,
isMasterDetail: state.app.isMasterDetail,
maxUsers: (state.settings.DirectMesssage_maxUsers as number) || 1,
createTeamPermission: state.permissions['create-team'],
createDirectMessagePermission: state.permissions['create-d'],
createPublicChannelPermission: state.permissions['create-c'],
createPrivateChannelPermission: state.permissions['create-p'],
createDiscussionPermission: state.permissions['start-discussion']
});
export default connect(mapStateToProps)(withTheme(NewMessageView));

View File

@ -0,0 +1,32 @@
import React from 'react';
import * as List from '../../containers/List';
import { themes } from '../../lib/constants';
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
import { useTheme } from '../../theme';
interface IButton {
onPress: () => void;
testID: string;
title: string;
icon: TIconsName;
}
const ButtonCreate = ({ onPress, testID, title, icon }: IButton) => {
const { theme } = useTheme();
return (
<>
<List.Item
onPress={onPress}
testID={testID}
left={() => <CustomIcon name={icon} size={24} color={themes[theme].bodyText} />}
right={() => <CustomIcon name={'chevron-right'} size={24} color={themes[theme].bodyText} />}
title={title}
/>
<List.Separator />
</>
);
};
export default ButtonCreate;

View File

@ -0,0 +1,107 @@
import { StackNavigationProp } from '@react-navigation/stack';
import React, { useCallback } from 'react';
import { StyleSheet, View } from 'react-native';
import { useDispatch } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import { createChannelRequest } from '../../actions/createChannel';
import { themes } from '../../lib/constants';
import SearchBox from '../../containers/SearchBox';
import I18n from '../../i18n';
import Navigation from '../../lib/navigation/appNavigation';
import { useTheme } from '../../theme';
import { events, logEvent } from '../../lib/methods/helpers/log';
import { NewMessageStackParamList } from '../../stacks/types';
import { compareServerVersion } from '../../lib/methods/helpers';
import { useAppSelector, usePermissions } from '../../lib/hooks';
import ButtonCreate from './ButtonCreate';
const styles = StyleSheet.create({
container: {
paddingTop: 16
},
buttonContainer: {
paddingBottom: 16
}
});
const HeaderNewMessage = ({ maxUsers, onChangeText }: { maxUsers: number; onChangeText: (text: string) => void }) => {
const navigation = useNavigation<StackNavigationProp<NewMessageStackParamList, 'NewMessageView'>>();
const dispatch = useDispatch();
const { theme } = useTheme();
const serverVersion = useAppSelector(state => state.server.version as string);
const [
createPublicChannelPermission,
createPrivateChannelPermission,
createTeamPermission,
createDirectMessagePermission,
createDiscussionPermission
] = usePermissions(['create-c', 'create-p', 'create-team', 'create-d', 'start-discussion']);
const createChannel = useCallback(() => {
logEvent(events.NEW_MSG_CREATE_CHANNEL);
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
}, [navigation]);
const createTeam = useCallback(() => {
logEvent(events.NEW_MSG_CREATE_TEAM);
navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true })
});
}, [navigation]);
const createGroupChat = useCallback(() => {
logEvent(events.NEW_MSG_CREATE_GROUP_CHAT);
navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => dispatch(createChannelRequest({ group: true })),
buttonText: I18n.t('Create'),
maxUsers
});
}, [dispatch, maxUsers, navigation]);
const createDiscussion = useCallback(() => {
logEvent(events.NEW_MSG_CREATE_DISCUSSION);
Navigation.navigate('CreateDiscussionView');
}, []);
return (
<>
<View style={[styles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}>
<View style={styles.buttonContainer}>
{createPublicChannelPermission || createPrivateChannelPermission ? (
<ButtonCreate
onPress={createChannel}
title={'Channel'}
icon={'channel-public'}
testID={'new-message-view-create-channel'}
/>
) : null}
{compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && createTeamPermission ? (
<ButtonCreate onPress={createTeam} title={'Team'} icon={'teams'} testID={'new-message-view-create-team'} />
) : null}
{maxUsers > 2 && createDirectMessagePermission ? (
<ButtonCreate
onPress={createGroupChat}
title={'Direct_message'}
icon={'message'}
testID={'new-message-view-create-direct-message'}
/>
) : null}
{createDiscussionPermission ? (
<ButtonCreate
onPress={createDiscussion}
title={'Discussion'}
icon={'discussions'}
testID={'new-message-view-create-discussion'}
/>
) : null}
</View>
</View>
<SearchBox onChangeText={(text: string) => onChangeText(text)} testID='new-message-view-search' />
</>
);
};
export default HeaderNewMessage;

View File

@ -0,0 +1,115 @@
import { Q } from '@nozbe/watermelondb';
import { StackNavigationProp } from '@react-navigation/stack';
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react';
import { FlatList } from 'react-native';
import { shallowEqual } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import { ISearch, TSubscriptionModel } from '../../definitions';
import I18n from '../../i18n';
import database from '../../lib/database';
import { useTheme } from '../../theme';
import { goRoom as goRoomMethod, TGoRoomItem } from '../../lib/methods/helpers/goRoom';
import log, { events, logEvent } from '../../lib/methods/helpers/log';
import { NewMessageStackParamList } from '../../stacks/types';
import { search as searchMethod } from '../../lib/methods';
import { useAppSelector } from '../../lib/hooks';
import UserItem from '../../containers/UserItem';
import HeaderNewMessage from './HeaderNewMessage';
const QUERY_SIZE = 50;
type TItem = ISearch | TSubscriptionModel;
const NewMessageView = () => {
const [chats, setChats] = useState<TSubscriptionModel[]>([]);
const [search, setSearch] = useState<TItem[]>([]);
const { colors } = useTheme();
const navigation = useNavigation<StackNavigationProp<NewMessageStackParamList, 'NewMessageView'>>();
const { isMasterDetail, maxUsers, useRealName } = useAppSelector(
state => ({
isMasterDetail: state.app.isMasterDetail,
maxUsers: (state.settings.DirectMesssage_maxUsers as number) || 1,
useRealName: state.settings.UI_Use_Real_Name as boolean
}),
shallowEqual
);
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />,
title: I18n.t('Create_New')
});
}, [navigation]);
useEffect(() => {
const init = async () => {
try {
const db = database.active;
const c = await db
.get('subscriptions')
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
.fetch();
setChats(c);
} catch (e) {
log(e);
}
};
init();
}, []);
const handleSearch = useCallback(async (text: string) => {
const result = (await searchMethod({ text, filterRooms: false })) as ISearch[];
setSearch(result);
}, []);
const goRoom = useCallback(
(item: TGoRoomItem) => {
logEvent(events.NEW_MSG_CHAT_WITH_USER);
if (isMasterDetail) {
navigation.pop();
}
goRoomMethod({ item, isMasterDetail });
},
[isMasterDetail, navigation]
);
return (
<SafeAreaView testID='new-message-view'>
<StatusBar />
<FlatList
data={search.length > 0 ? search : chats}
keyExtractor={item => item._id || item.rid}
ListHeaderComponent={<HeaderNewMessage maxUsers={maxUsers} onChangeText={handleSearch} />}
renderItem={({ item }) => {
const itemSearch = item as ISearch;
const itemModel = item as TSubscriptionModel;
return (
<UserItem
name={useRealName && itemSearch.fname ? itemSearch.fname : itemModel.name}
username={itemSearch.search ? itemSearch.username : itemModel.name}
onPress={() => goRoom(itemModel)}
testID={`new-message-view-item-${item.name}`}
/>
);
}}
ItemSeparatorComponent={List.Separator}
ListFooterComponent={List.Separator}
contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
keyboardShouldPersistTaps='always'
/>
</SafeAreaView>
);
};
export default NewMessageView;

View File

@ -705,7 +705,7 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
) : null} ) : null}
{room.broadcast {room.broadcast
? [ ? [
<Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>, <Text style={styles.broadcast}>{I18n.t('Broadcast')}</Text>,
<View style={[styles.divider, { borderColor: themes[theme].separatorColor }]} /> <View style={[styles.divider, { borderColor: themes[theme].separatorColor }]} />
] ]
: null} : null}

View File

@ -24,8 +24,8 @@ const Channel = ({ room }: { room: ISubscription }) => {
testID='room-info-view-announcement' testID='room-info-view-announcement'
/> />
<Item <Item
label={I18n.t('Broadcast_Channel')} label={I18n.t('Broadcast')}
content={room.broadcast ? I18n.t('Broadcast_channel_Description') : ''} content={room.broadcast ? I18n.t('Broadcast_hint') : ''}
testID='room-info-view-broadcast' testID='room-info-view-broadcast'
/> />
</> </>

View File

@ -120,6 +120,7 @@ const removeFromTeam = async (
updateState({ updateState({
members: newMembers members: newMembers
}); });
appNavigation.navigate('RoomMembersView', { room });
} }
} catch (e: any) { } catch (e: any) {
log(e); log(e);

View File

@ -1,291 +0,0 @@
import { Q } from '@nozbe/watermelondb';
import orderBy from 'lodash/orderBy';
import React from 'react';
import { FlatList, View } from 'react-native';
import { connect } from 'react-redux';
import { Subscription } from 'rxjs';
import { addUser, removeUser, reset } from '../actions/selectedUsers';
import { themes } from '../lib/constants';
import * as HeaderButton from '../containers/HeaderButton';
import * as List from '../containers/List';
import { sendLoadingEvent } from '../containers/Loading';
import SafeAreaView from '../containers/SafeAreaView';
import SearchBox from '../containers/SearchBox';
import StatusBar from '../containers/StatusBar';
import { IApplicationState, IBaseScreen, ISearch, ISearchLocal, IUser } from '../definitions';
import I18n from '../i18n';
import database from '../lib/database';
import UserItem from '../containers/UserItem';
import { ISelectedUser } from '../reducers/selectedUsers';
import { getUserSelector } from '../selectors/login';
import { ChatsStackParamList } from '../stacks/types';
import { withTheme } from '../theme';
import { showErrorAlert } from '../lib/methods/helpers/info';
import log, { events, logEvent } from '../lib/methods/helpers/log';
import sharedStyles from './Styles';
import { search } from '../lib/methods';
import { isGroupChat } from '../lib/methods/helpers';
const ITEM_WIDTH = 250;
const getItemLayout = (_: any, index: number) => ({ length: ITEM_WIDTH, offset: ITEM_WIDTH * index, index });
interface ISelectedUsersViewState {
maxUsers?: number;
search: (ISearch | ISearchLocal)[];
chats: ISelectedUser[];
}
interface ISelectedUsersViewProps extends IBaseScreen<ChatsStackParamList, 'SelectedUsersView'> {
users: ISelectedUser[];
loading: boolean;
user: IUser;
baseUrl: string;
}
class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelectedUsersViewState> {
private flatlist?: FlatList;
private querySubscription?: Subscription;
constructor(props: ISelectedUsersViewProps) {
super(props);
this.init();
const maxUsers = props.route.params?.maxUsers;
this.state = {
maxUsers,
search: [],
chats: []
};
const { user, dispatch } = this.props;
if (this.isGroupChat()) {
dispatch(addUser({ _id: user.id, name: user.username, fname: user.name as string }));
}
this.setHeader(props.route.params?.showButton);
}
componentDidUpdate(prevProps: ISelectedUsersViewProps) {
const { users, loading } = this.props;
if (this.isGroupChat()) {
if (prevProps.users.length !== users.length) {
this.setHeader(users.length > 0);
}
}
if (loading !== prevProps.loading) {
sendLoadingEvent({ visible: loading });
}
}
componentWillUnmount() {
const { dispatch } = this.props;
dispatch(reset());
if (this.querySubscription && this.querySubscription.unsubscribe) {
this.querySubscription.unsubscribe();
}
}
// showButton can be sent as route params or updated by the component
setHeader = (showButton?: boolean) => {
const { navigation, route } = this.props;
const title = route.params?.title ?? I18n.t('Select_Users');
const buttonText = route.params?.buttonText ?? I18n.t('Next');
const maxUsers = route.params?.maxUsers;
const nextAction = route.params?.nextAction ?? (() => {});
const options = {
title,
headerRight: () =>
(!maxUsers || showButton) && (
<HeaderButton.Container>
<HeaderButton.Item title={buttonText} onPress={nextAction} testID='selected-users-view-submit' />
</HeaderButton.Container>
)
};
navigation.setOptions(options);
};
// eslint-disable-next-line react/sort-comp
init = async () => {
try {
const db = database.active;
const observable = await db.get('subscriptions').query(Q.where('t', 'd')).observeWithColumns(['room_updated_at']);
this.querySubscription = observable.subscribe(data => {
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']) as ISelectedUser[];
this.setState({ chats });
});
} catch (e) {
log(e);
}
};
onSearchChangeText(text: string) {
this.handleSearch(text);
}
handleSearch = async (text: string) => {
const result = await search({ text, filterRooms: false });
this.setState({
search: result
});
};
isGroupChat = () => {
const { maxUsers } = this.state;
return maxUsers && maxUsers > 2;
};
isChecked = (username: string) => {
const { users } = this.props;
return users.findIndex(el => el.name === username) !== -1;
};
toggleUser = (user: ISelectedUser) => {
const { maxUsers } = this.state;
const {
dispatch,
users,
user: { username }
} = this.props;
// Disallow removing self user from the direct message group
if (this.isGroupChat() && username === user.name) {
return;
}
if (!this.isChecked(user.name)) {
if (this.isGroupChat() && users.length === maxUsers) {
return showErrorAlert(I18n.t('Max_number_of_users_allowed_is_number', { maxUsers }), I18n.t('Oops'));
}
logEvent(events.SELECTED_USERS_ADD_USER);
dispatch(addUser(user));
} else {
logEvent(events.SELECTED_USERS_REMOVE_USER);
dispatch(removeUser(user));
}
};
_onPressItem = (id: string, item = {} as ISelectedUser) => {
if (item.search) {
this.toggleUser({ _id: item._id, name: item.username as string, fname: item.name });
} else {
this.toggleUser({ _id: item._id, name: item.name, fname: item.fname });
}
};
_onPressSelectedItem = (item: ISelectedUser) => this.toggleUser(item);
renderHeader = () => {
const { theme } = this.props;
return (
<View style={{ backgroundColor: themes[theme].backgroundColor }}>
<SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='select-users-view-search' />
{this.renderSelected()}
</View>
);
};
setFlatListRef = (ref: FlatList) => (this.flatlist = ref);
onContentSizeChange = () => this.flatlist?.scrollToEnd({ animated: true });
renderSelected = () => {
const { users, theme } = this.props;
if (users.length === 0) {
return null;
}
return (
<FlatList
data={users}
ref={this.setFlatListRef}
onContentSizeChange={this.onContentSizeChange}
getItemLayout={getItemLayout}
keyExtractor={item => item._id}
style={[sharedStyles.separatorTop, { borderColor: themes[theme].separatorColor }]}
contentContainerStyle={{ marginVertical: 5 }}
renderItem={this.renderSelectedItem}
keyboardShouldPersistTaps='always'
horizontal
/>
);
};
renderSelectedItem = ({ item }: { item: ISelectedUser }) => (
<UserItem
name={item.fname}
username={item.name}
onPress={() => this._onPressSelectedItem(item)}
testID={`selected-user-${item.name}`}
style={{ paddingRight: 15 }}
/>
);
renderItem = ({ item, index }: { item: ISelectedUser; index: number }) => {
const { search, chats } = this.state;
const { theme } = this.props;
const name = item.search ? item.name : item.fname;
const username = item.search ? (item.username as string) : item.name;
let style = { borderColor: themes[theme].separatorColor };
if (index === 0) {
style = { ...style, ...sharedStyles.separatorTop };
}
if (search.length > 0 && index === search.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
if (search.length === 0 && index === chats.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
return (
<UserItem
name={name}
username={username}
onPress={() => this._onPressItem(item._id, item)}
testID={`select-users-view-item-${item.name}`}
icon={this.isChecked(username) ? 'check' : null}
style={style}
/>
);
};
renderList = () => {
const { search, chats } = this.state;
const { theme } = this.props;
const searchOrChats = (search.length > 0 ? search : chats) as ISelectedUser[];
// filter DM between multiple users
const data = searchOrChats.filter(sub => !isGroupChat(sub));
return (
<FlatList
data={data}
extraData={this.props}
keyExtractor={item => item._id}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={this.renderHeader}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>
);
};
render() {
return (
<SafeAreaView testID='select-users-view'>
<StatusBar />
{this.renderList()}
</SafeAreaView>
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
baseUrl: state.server.server,
users: state.selectedUsers.users,
loading: state.selectedUsers.loading,
user: getUserSelector(state)
});
export default connect(mapStateToProps)(withTheme(SelectedUsersView));

View File

@ -0,0 +1,74 @@
import React, { useRef } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
import { themes } from '../../lib/constants';
import SearchBox from '../../containers/SearchBox';
import I18n from '../../i18n';
import { ISelectedUser } from '../../reducers/selectedUsers';
import { useTheme } from '../../theme';
import sharedStyles from '../Styles';
import { useAppSelector } from '../../lib/hooks';
import Chip from '../../containers/Chip';
const styles = StyleSheet.create({
selectedText: {
marginLeft: 16,
marginBottom: 12,
fontSize: 12,
...sharedStyles.textRegular
},
contentContainerList: {
paddingHorizontal: 16,
marginBottom: 16
}
});
const Header = ({
onChangeText,
useRealName,
onPressItem
}: {
useRealName: boolean;
onChangeText: (text: string) => void;
onPressItem: (userItem: ISelectedUser) => void;
}) => {
const flatlist = useRef<FlatList>();
const { theme } = useTheme();
const { users } = useAppSelector(state => ({
users: state.selectedUsers.users
}));
const onContentSizeChange = () => flatlist?.current?.scrollToEnd({ animated: true });
return (
<View style={{ backgroundColor: themes[theme].backgroundColor }}>
<SearchBox onChangeText={(text: string) => onChangeText(text)} testID='select-users-view-search' />
{users.length === 0 ? null : (
<View>
<Text style={[styles.selectedText, { color: themes[theme].auxiliaryTintColor }]}>
{I18n.t('N_Selected_members', { n: users.length })}
</Text>
<FlatList
data={users}
ref={(ref: FlatList) => (flatlist.current = ref)}
onContentSizeChange={onContentSizeChange}
keyExtractor={item => item._id}
renderItem={({ item }) => {
const name = useRealName && item.fname ? item.fname : item.name;
const username = item.search ? (item.username as string) : item.name;
return (
<Chip text={name} avatar={username} onPress={() => onPressItem(item)} testID={`selected-user-${item.name}`} />
);
}}
keyboardShouldPersistTaps='always'
contentContainerStyle={styles.contentContainerList}
horizontal
/>
</View>
)}
</View>
);
};
export default Header;

View File

@ -0,0 +1,180 @@
import { Q } from '@nozbe/watermelondb';
import orderBy from 'lodash/orderBy';
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react';
import { FlatList } from 'react-native';
import { shallowEqual, useDispatch } from 'react-redux';
import { Subscription } from 'rxjs';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { addUser, removeUser, reset } from '../../actions/selectedUsers';
import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List';
import { sendLoadingEvent } from '../../containers/Loading';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import { ISearch, ISearchLocal } from '../../definitions';
import I18n from '../../i18n';
import database from '../../lib/database';
import UserItem from '../../containers/UserItem';
import { ISelectedUser } from '../../reducers/selectedUsers';
import { getUserSelector } from '../../selectors/login';
import { ChatsStackParamList } from '../../stacks/types';
import { useTheme } from '../../theme';
import { showErrorAlert } from '../../lib/methods/helpers/info';
import log, { events, logEvent } from '../../lib/methods/helpers/log';
import { search as searchMethod } from '../../lib/methods';
import { isGroupChat as isGroupChatMethod } from '../../lib/methods/helpers';
import { useAppSelector } from '../../lib/hooks';
import Header from './Header';
type TRoute = RouteProp<ChatsStackParamList, 'SelectedUsersView'>;
type TNavigation = StackNavigationProp<ChatsStackParamList, 'SelectedUsersView'>;
type TSearchItem = ISearch | ISearchLocal;
const SelectedUsersView = () => {
const [chats, setChats] = useState<ISelectedUser[]>([]);
const [search, setSearch] = useState<TSearchItem[]>([]);
const { maxUsers, showButton, title, buttonText, nextAction } = useRoute<TRoute>().params;
const navigation = useNavigation<TNavigation>();
const { colors } = useTheme();
const dispatch = useDispatch();
const { users, loading, useRealName, user } = useAppSelector(
state => ({
users: state.selectedUsers.users,
loading: state.selectedUsers.loading,
useRealName: state.settings.UI_Use_Real_Name as boolean,
user: getUserSelector(state)
}),
shallowEqual
);
useEffect(() => {
sendLoadingEvent({ visible: loading });
}, [loading]);
const isChecked = (username: string) => users.findIndex(el => el.name === username) !== -1;
const isGroupChat = () => maxUsers && maxUsers > 2;
useLayoutEffect(() => {
const titleHeader = title ?? I18n.t('Select_Members');
const buttonTextHeader = buttonText ?? I18n.t('Next');
const nextActionHeader = nextAction ?? (() => {});
const options = {
title: titleHeader,
headerRight: () =>
(!maxUsers || showButton || (isGroupChat() && users.length > 1)) && (
<HeaderButton.Container>
<HeaderButton.Item
title={users.length > 0 ? buttonTextHeader : I18n.t('Skip')}
onPress={nextActionHeader}
testID='selected-users-view-submit'
/>
</HeaderButton.Container>
)
};
navigation.setOptions(options);
}, [navigation, users.length, maxUsers]);
useEffect(() => {
if (isGroupChat()) {
dispatch(addUser({ _id: user.id, name: user.username, fname: user.name as string }));
}
}, []);
useEffect(() => {
let querySubscription: Subscription;
const init = async () => {
try {
const db = database.active;
const observable = await db.get('subscriptions').query(Q.where('t', 'd')).observeWithColumns(['room_updated_at']);
querySubscription = observable.subscribe(data => {
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']) as ISelectedUser[];
setChats(chats);
});
} catch (e) {
log(e);
}
};
init();
return () => {
dispatch(reset());
if (querySubscription && querySubscription.unsubscribe) {
querySubscription.unsubscribe();
}
};
}, [dispatch]);
const handleSearch = useCallback(async (text: string) => {
const result = await searchMethod({ text, filterRooms: false });
setSearch(result);
}, []);
const toggleUser = (userItem: ISelectedUser) => {
// Disallow removing self user from the direct message group
if (isGroupChat() && user.username === userItem.name) {
return;
}
if (!isChecked(userItem.name)) {
if (isGroupChat() && users.length === maxUsers) {
return showErrorAlert(I18n.t('Max_number_of_users_allowed_is_number', { maxUsers }), I18n.t('Oops'));
}
logEvent(events.SELECTED_USERS_ADD_USER);
dispatch(addUser(userItem));
} else {
logEvent(events.SELECTED_USERS_REMOVE_USER);
dispatch(removeUser(userItem));
}
};
const _onPressItem = (item = {} as ISelectedUser) => {
if (item.search) {
toggleUser({ _id: item._id, name: item.username as string, fname: item.name });
} else {
toggleUser({ _id: item._id, name: item.name, fname: item.fname });
}
};
const searchOrChats = (search.length > 0 ? search : chats) as ISelectedUser[];
// filter DM between multiple users
const data = searchOrChats.filter(sub => !isGroupChatMethod(sub));
return (
<SafeAreaView testID='select-users-view'>
<StatusBar />
<FlatList
data={data}
keyExtractor={item => item._id}
renderItem={({ item }) => {
const name = useRealName && item.fname ? item.fname : item.name;
const username = item.search ? (item.username as string) : item.name;
return (
<UserItem
name={name}
username={username}
onPress={() => _onPressItem(item)}
testID={`select-users-view-item-${item.name}`}
icon={isChecked(username) ? 'checkbox-checked' : 'checkbox-unchecked'}
iconColor={isChecked(username) ? colors.actionTintColor : colors.separatorColor}
/>
);
}}
ItemSeparatorComponent={List.Separator}
ListFooterComponent={<List.Separator />}
ListHeaderComponent={<Header useRealName={useRealName} onChangeText={handleSearch} onPressItem={toggleUser} />}
contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
keyboardShouldPersistTaps='always'
/>
</SafeAreaView>
);
};
export default SelectedUsersView;

View File

@ -97,9 +97,10 @@ async function mockMessage(message, isThread = false) {
await element(by.id(input)).replaceText(`${data.random}${message}`); await element(by.id(input)).replaceText(`${data.random}${message}`);
await sleep(300); await sleep(300);
await element(by.id('messagebox-send-message')).tap(); await element(by.id('messagebox-send-message')).tap();
await sleep(500);
await waitFor(element(by[textMatcher](`${data.random}${message}`))) await waitFor(element(by[textMatcher](`${data.random}${message}`)))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(10000);
await element(by[textMatcher](`${data.random}${message}`)) await element(by[textMatcher](`${data.random}${message}`))
.atIndex(0) .atIndex(0)
.tap(); .tap();

View File

@ -57,7 +57,7 @@ describe('Broadcast room', () => {
await waitFor(element(by.id('room-info-view'))) await waitFor(element(by.id('room-info-view')))
.toBeVisible() .toBeVisible()
.withTimeout(2000); .withTimeout(2000);
await expect(element(by.label('Broadcast Channel').withAncestor(by.id('room-info-view-broadcast')))).toBeVisible(); await expect(element(by.label('Broadcast').withAncestor(by.id('room-info-view-broadcast')))).toBeVisible();
await tapBack(); await tapBack();
await waitFor(element(by.id('room-actions-view'))) await waitFor(element(by.id('room-actions-view')))
.toBeVisible() .toBeVisible()

View File

@ -10,7 +10,7 @@ async function waitForToast() {
// await expect(element(by.id('toast'))).toBeVisible(); // await expect(element(by.id('toast'))).toBeVisible();
// await waitFor(element(by.id('toast'))).not.toBeNotVisible().withTimeout(1000); // await waitFor(element(by.id('toast'))).not.toBeNotVisible().withTimeout(1000);
// await expect(element(by.id('toast'))).not.toBeVisible(); // await expect(element(by.id('toast'))).not.toBeVisible();
await sleep(300); await sleep(600);
} }
describe('Profile screen', () => { describe('Profile screen', () => {

View File

@ -29,7 +29,7 @@ describe('Discussion', () => {
await waitFor(element(by.id('new-message-view'))) await waitFor(element(by.id('new-message-view')))
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by[textMatcher]('Create Discussion')).atIndex(0).tap(); await element(by[textMatcher]('Discussion')).atIndex(0).tap();
await waitFor(element(by.id('create-discussion-view'))) await waitFor(element(by.id('create-discussion-view')))
.toExist() .toExist()
.withTimeout(60000); .withTimeout(60000);

View File

@ -34,7 +34,7 @@ describe('Group DM', () => {
describe('Usage', () => { describe('Usage', () => {
it('should navigate to create DM', async () => { it('should navigate to create DM', async () => {
await element(by[textMatcher]('Create Direct Messages')).tap(); await element(by[textMatcher]('Direct message')).atIndex(0).tap();
}); });
it('should add users', async () => { it('should add users', async () => {

View File

@ -175,7 +175,7 @@ describe('Room info screen', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}`); await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}`);
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await swipe('down'); await swipe('down');
@ -188,8 +188,8 @@ describe('Room info screen', () => {
await element(by.id('room-info-edit-view-announcement')).replaceText('abc'); await element(by.id('room-info-edit-view-announcement')).replaceText('abc');
await element(by.id('room-info-edit-view-password')).replaceText('abc'); await element(by.id('room-info-edit-view-password')).replaceText('abc');
await element(by.id('room-info-edit-view-t')).tap(); await element(by.id('room-info-edit-view-t')).tap();
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-ro')).longPress(); // https://github.com/facebook/react-native/issues/28032 // await element(by.id('room-info-edit-view-ro')).longPress(); // https://github.com/facebook/react-native/issues/28032
await element(by.id('room-info-edit-view-react-when-ro')).tap(); await element(by.id('room-info-edit-view-react-when-ro')).tap();
await swipe('up'); await swipe('up');
await element(by.id('room-info-edit-view-reset')).tap(); await element(by.id('room-info-edit-view-reset')).tap();
@ -201,14 +201,14 @@ describe('Room info screen', () => {
await expect(element(by.id('room-info-edit-view-password'))).toHaveText(''); await expect(element(by.id('room-info-edit-view-password'))).toHaveText('');
// await swipe('down'); // await swipe('down');
await expect(element(by.id('room-info-edit-view-t'))).toHaveToggleValue(true); await expect(element(by.id('room-info-edit-view-t'))).toHaveToggleValue(true);
await expect(element(by.id('room-info-edit-view-ro'))).toHaveToggleValue(false); await expect(element(by.id('room-info-edit-view-ro'))).toHaveToggleValue(true);
await expect(element(by.id('room-info-edit-view-react-when-ro'))).toBeNotVisible(); await expect(element(by.id('room-info-edit-view-react-when-ro'))).toHaveToggleValue(false);
await swipe('down'); await swipe('down');
}); });
it('should change room description', async () => { it('should change room description', async () => {
await element(by.id('room-info-edit-view-description')).replaceText('new description'); await element(by.id('room-info-edit-view-description')).replaceText('new description');
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await tapBack(); await tapBack();
@ -227,7 +227,7 @@ describe('Room info screen', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-info-edit-view-topic')).replaceText('new topic'); await element(by.id('room-info-edit-view-topic')).replaceText('new topic');
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await tapBack(); await tapBack();
@ -246,7 +246,7 @@ describe('Room info screen', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement'); await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement');
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await tapBack(); await tapBack();
@ -265,7 +265,7 @@ describe('Room info screen', () => {
.toExist() .toExist()
.withTimeout(2000); .withTimeout(2000);
await element(by.id('room-info-edit-view-password')).replaceText('password'); await element(by.id('room-info-edit-view-password')).replaceText('password');
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
}); });
@ -273,12 +273,12 @@ describe('Room info screen', () => {
it('should change room type', async () => { it('should change room type', async () => {
await swipe('down'); await swipe('down');
await element(by.id('room-info-edit-view-t')).tap(); await element(by.id('room-info-edit-view-t')).tap();
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
await swipe('down'); await swipe('down');
await element(by.id('room-info-edit-view-t')).tap(); await element(by.id('room-info-edit-view-t')).tap();
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-submit')).tap(); await element(by.id('room-info-edit-view-submit')).tap();
await waitForToast(); await waitForToast();
}); });
@ -298,7 +298,7 @@ describe('Room info screen', () => {
}); });
it('should delete room', async () => { it('should delete room', async () => {
await swipe('up'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5);
await element(by.id('room-info-edit-view-delete')).tap(); await element(by.id('room-info-edit-view-delete')).tap();
await waitFor(element(by[textMatcher]('Yes, delete it!'))) await waitFor(element(by[textMatcher]('Yes, delete it!')))
.toExist() .toExist()

View File

@ -15,6 +15,9 @@ describe('Create team screen', () => {
describe('New Message', () => { describe('New Message', () => {
before(async () => { before(async () => {
await waitFor(element(by.id('rooms-list-view-create-channel')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('rooms-list-view-create-channel')).tap(); await element(by.id('rooms-list-view-create-channel')).tap();
}); });

View File

@ -31,6 +31,7 @@
"@bugsnag/react-native": "^7.10.5", "@bugsnag/react-native": "^7.10.5",
"@codler/react-native-keyboard-aware-scroll-view": "^2.0.1", "@codler/react-native-keyboard-aware-scroll-view": "^2.0.1",
"@gorhom/bottom-sheet": "^4.3.1", "@gorhom/bottom-sheet": "^4.3.1",
"@hookform/resolvers": "^2.9.7",
"@nozbe/watermelondb": "0.23.0", "@nozbe/watermelondb": "0.23.0",
"@react-native-async-storage/async-storage": "^1.17.9", "@react-native-async-storage/async-storage": "^1.17.9",
"@react-native-clipboard/clipboard": "^1.8.5", "@react-native-clipboard/clipboard": "^1.8.5",
@ -79,6 +80,7 @@
"pretty-bytes": "5.6.0", "pretty-bytes": "5.6.0",
"prop-types": "15.7.2", "prop-types": "15.7.2",
"react": "17.0.2", "react": "17.0.2",
"react-hook-form": "^7.34.2",
"react-native": "RocketChat/react-native#0.68.2", "react-native": "RocketChat/react-native#0.68.2",
"react-native-animatable": "^1.3.3", "react-native-animatable": "^1.3.3",
"react-native-background-timer": "2.4.1", "react-native-background-timer": "2.4.1",
@ -136,7 +138,8 @@
"uri-js": "^4.4.1", "uri-js": "^4.4.1",
"url-parse": "1.5.10", "url-parse": "1.5.10",
"use-deep-compare-effect": "1.6.1", "use-deep-compare-effect": "1.6.1",
"xregexp": "5.0.2" "xregexp": "5.0.2",
"yup": "^0.32.11"
}, },
"resolutions": { "resolutions": {
"ua-parser-js": "^1.0.2", "ua-parser-js": "^1.0.2",

View File

@ -3005,7 +3005,7 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.14.8", "@babel/runtime@^7.17.8", "@babel/runtime@^7.7.6": "@babel/runtime@^7.14.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.7.6":
version "7.18.9" version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
@ -3944,6 +3944,11 @@
dependencies: dependencies:
"@hapi/hoek" "^9.0.0" "@hapi/hoek" "^9.0.0"
"@hookform/resolvers@^2.9.7":
version "2.9.7"
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-2.9.7.tgz#8b257ae67234ce0270e6b044c1a61fb98ec02b4b"
integrity sha512-BloehX3MOLwuFEwT4yZnmolPjVmqyn8VsSuodLfazbCIqxBHsQ4qUZsi+bvNNCduRli1AGWFrkDLGD5QoNzsoA==
"@humanwhocodes/config-array@^0.5.0": "@humanwhocodes/config-array@^0.5.0":
version "0.5.0" version "0.5.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
@ -6083,6 +6088,11 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw== integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
"@types/lodash@^4.14.175":
version "4.14.182"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
"@types/long@*": "@types/long@*":
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
@ -14091,6 +14101,11 @@ locate-path@^6.0.0:
dependencies: dependencies:
p-locate "^5.0.0" p-locate "^5.0.0"
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash.assign@^4.2.0: lodash.assign@^4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
@ -15090,6 +15105,11 @@ nan@^2.14.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nanoclone@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4"
integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==
nanoid@3.1.23: nanoid@3.1.23:
version "3.1.23" version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
@ -16537,6 +16557,11 @@ proper-lockfile@^3.0.2:
retry "^0.12.0" retry "^0.12.0"
signal-exit "^3.0.2" signal-exit "^3.0.2"
property-expr@^2.0.4:
version "2.0.5"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4"
integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==
property-information@^5.0.0: property-information@^5.0.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943"
@ -16890,6 +16915,11 @@ react-helmet-async@^1.0.7:
react-fast-compare "^3.2.0" react-fast-compare "^3.2.0"
shallowequal "^1.1.0" shallowequal "^1.1.0"
react-hook-form@^7.34.2:
version "7.34.2"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.34.2.tgz#9ac6d1a309a7c4aaa369d1269357a70e9e9bf4de"
integrity sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==
react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -19507,6 +19537,11 @@ topo@2.x.x:
dependencies: dependencies:
hoek "4.x.x" hoek "4.x.x"
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==
tr46@~0.0.3: tr46@~0.0.3:
version "0.0.3" version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
@ -20797,6 +20832,19 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
yup@^0.32.11:
version "0.32.11"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5"
integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==
dependencies:
"@babel/runtime" "^7.15.4"
"@types/lodash" "^4.14.175"
lodash "^4.17.21"
lodash-es "^4.17.21"
nanoclone "^0.2.1"
property-expr "^2.0.4"
toposort "^2.0.2"
zwitch@^1.0.0: zwitch@^1.0.0:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"