diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js
index 4f02264ec..1baa14ff4 100644
--- a/.storybook/storybook.requires.js
+++ b/.storybook/storybook.requires.js
@@ -22,6 +22,7 @@ const getStories = () => {
require("../app/containers/Avatar/Avatar.stories.tsx"),
require("../app/containers/BackgroundContainer/index.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/List/List.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/UnreadBadge/UnreadBadge.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/RoomView/LoadMore/LoadMore.stories.tsx"),
require("../app/views/ThreadMessagesView/Item.stories.tsx"),
diff --git a/__tests__/containers/Chip/__snapshots__/Chip.stories.storyshot b/__tests__/containers/Chip/__snapshots__/Chip.stories.storyshot
new file mode 100644
index 000000000..83a6eb4de
--- /dev/null
+++ b/__tests__/containers/Chip/__snapshots__/Chip.stories.storyshot
@@ -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\\"]}]}]}]}]}"`;
diff --git a/__tests__/views/CreateChannelView/RoomSettings/__snapshots__/SwitchItem.stories.storyshot b/__tests__/views/CreateChannelView/RoomSettings/__snapshots__/SwitchItem.stories.storyshot
new file mode 100644
index 000000000..4012a3d13
--- /dev/null
+++ b/__tests__/views/CreateChannelView/RoomSettings/__snapshots__/SwitchItem.stories.storyshot
@@ -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}]}]}"`;
diff --git a/app/containers/Chip/Chip.stories.tsx b/app/containers/Chip/Chip.stories.tsx
new file mode 100644
index 000000000..f4a83eda1
--- /dev/null
+++ b/app/containers/Chip/Chip.stories.tsx
@@ -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) => (
+
+
+
+);
+
+export const ChipText = () => {}} />;
+
+export const ChipWithShortText = () => {}} />;
+
+export const ChipWithoutAvatar = () => {}} />;
+
+export const ChipWithoutIcon = () => ;
+
+export const ChipWithoutAvatarAndIcon = () => ;
diff --git a/app/containers/Chip/Chip.test.tsx b/app/containers/Chip/Chip.test.tsx
new file mode 100644
index 000000000..c222b91c3
--- /dev/null
+++ b/app/containers/Chip/Chip.test.tsx
@@ -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) => (
+
+
+
+);
+
+describe('Chips', () => {
+ it('should render the Chip component', () => {
+ const { findByTestId } = render(
+ testChip.onPress(testChip.item)}
+ />
+ );
+
+ expect(findByTestId(testChip.testID)).toBeTruthy();
+ });
+ it("should not call onPress if it's not passed", async () => {
+ const { findByTestId } = render();
+
+ const component = await findByTestId(testChip.testID);
+ fireEvent.press(component);
+ expect(onPressMock).not.toHaveBeenCalled();
+ });
+ it('should tap Chip and return item', async () => {
+ const { findByTestId } = render(
+ testChip.onPress(testChip.item)}
+ />
+ );
+
+ const component = await findByTestId(testChip.testID);
+ fireEvent.press(component);
+ expect(onPressMock).toHaveBeenCalled();
+ expect(onPressMock).toHaveReturnedWith(testChip.item);
+ });
+});
diff --git a/app/containers/Chip/index.tsx b/app/containers/Chip/index.tsx
new file mode 100644
index 000000000..a01dfc258
--- /dev/null
+++ b/app/containers/Chip/index.tsx
@@ -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;
+}
+
+const Chip = ({ avatar, text, onPress, testID, style }: IChip) => {
+ const { colors } = useTheme();
+
+ return (
+ [
+ styles.pressable,
+ {
+ backgroundColor: pressed ? colors.bannerBackground : colors.auxiliaryBackground
+ },
+ style
+ ]}
+ disabled={!onPress}
+ onPress={() => onPress?.()}
+ android_ripple={{
+ color: colors.bannerBackground
+ }}
+ >
+
+ {avatar ? : null}
+
+
+ {text}
+
+
+ {onPress ? : null}
+
+
+ );
+};
+
+export default Chip;
diff --git a/app/containers/TextInput/ControlledFormTextInput.tsx b/app/containers/TextInput/ControlledFormTextInput.tsx
new file mode 100644
index 000000000..5b69b0e20
--- /dev/null
+++ b/app/containers/TextInput/ControlledFormTextInput.tsx
@@ -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;
+ name: string;
+}
+
+export const ControlledFormTextInput = ({ control, name, ...props }: IControlledFormTextInputProps) => (
+ }
+ />
+);
diff --git a/app/containers/TextInput/index.ts b/app/containers/TextInput/index.ts
index 48a640fc2..a9b215823 100644
--- a/app/containers/TextInput/index.ts
+++ b/app/containers/TextInput/index.ts
@@ -1,2 +1,3 @@
export * from './TextInput';
export * from './FormTextInput';
+export * from './ControlledFormTextInput';
diff --git a/app/containers/UserItem.tsx b/app/containers/UserItem.tsx
index e044dd6ba..02739d2f8 100644
--- a/app/containers/UserItem.tsx
+++ b/app/containers/UserItem.tsx
@@ -25,13 +25,9 @@ const styles = StyleSheet.create({
marginRight: 15
},
name: {
- fontSize: 17,
+ fontSize: 16,
...sharedStyles.textMedium
},
- username: {
- fontSize: 14,
- ...sharedStyles.textRegular
- },
icon: {
marginHorizontal: 15,
alignSelf: 'center'
@@ -46,10 +42,12 @@ interface IUserItem {
onLongPress?: () => void;
style?: StyleProp;
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();
+
return (
-
+
{name}
-
- @{username}
-
- {icon ? : null}
+ {icon ? : null}
);
diff --git a/app/i18n/locales/ar.json b/app/i18n/locales/ar.json
index 2315fbf7e..ecfc038dd 100644
--- a/app/i18n/locales/ar.json
+++ b/app/i18n/locales/ar.json
@@ -117,8 +117,7 @@
"Black": "أسود",
"Block_user": "حظر المستخدم",
"Browser": "المتصفح",
- "Broadcast_channel_Description": "يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد",
- "Broadcast_Channel": "قناة البث",
+ "Broadcast_hint": "يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد",
"Busy": "مشغول",
"By_proceeding_you_are_agreeing": "من خلال المتابعة، أنت توافق على",
"Cancel_editing": "إلغاء التعديل",
@@ -389,7 +388,6 @@
"Preferences": "التفضيلات",
"Preferences_saved": "تم حفظ التفضيلات",
"Privacy_Policy": "سياسة الخصوصية",
- "Private_Channel": "قناة خاصة",
"Private": "خاص",
"Processing": "جار معالجة...",
"Profile_saved_successfully": "تم حفظ الملف الشخصي بنجاح!",
@@ -403,7 +401,6 @@
"Reactions": "التفاعلات",
"Read_External_Permission_Message": "يحتاج Rocket.chat للوصول إلى الصور والملفات الموجودة على الجهاز",
"Read_External_Permission": "صلاحية قراءة الوسائط",
- "Read_Only_Channel": "قناة للقراءة فقط",
"Read_Only": "قراءة فقط",
"Read_Receipt": "قراءة المستلم",
"Receive_Group_Mentions": "تلقي إشارات المجموعة",
diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json
index 17e7ad23e..11c5c34c5 100644
--- a/app/i18n/locales/de.json
+++ b/app/i18n/locales/de.json
@@ -119,8 +119,7 @@
"Black": "Schwarz",
"Block_user": "Benutzer blockieren",
"Browser": "Browser",
- "Broadcast_channel_Description": "Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten",
- "Broadcast_Channel": "Broadcast-Kanal",
+ "Broadcast_hint": "Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten",
"Busy": "Beschäftigt",
"By_proceeding_you_are_agreeing": "Indem Sie fortfahren, akzeptieren Sie unsere",
"Cancel_editing": "Bearbeitung abbrechen",
@@ -394,7 +393,6 @@
"Preferences": "Einstellungen",
"Preferences_saved": "Einstellungen gespeichert!",
"Privacy_Policy": " Datenschutzbestimmungen",
- "Private_Channel": "Privater Kanal",
"Private": "Privat",
"Processing": "Bearbeite …",
"Profile_saved_successfully": "Profil erfolgreich gespeichert!",
@@ -409,7 +407,6 @@
"Reactions": "Reaktionen",
"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_Only_Channel": "Nur-Lese-Kanal",
"Read_Only": "Schreibgeschützt",
"Read_Receipt": "Lesebestätigung",
"Receive_Group_Mentions": "Gruppen-Benachrichtigungen erhalten",
@@ -705,9 +702,6 @@
"Team_not_found": "Team nicht gefunden",
"Create_Team": "Team erstellen",
"Team_Name": "Team-Name",
- "Private_Team": "Privates Team",
- "Read_Only_Team": "Nur-Lesen-Team",
- "Broadcast_Team": "Broadcast-Team",
"creating_team": "Team erstellen",
"team-name-already-exists": "Ein Team mit diesem Namen existiert bereits",
"Add_Channel_to_Team": "Kanal zum Team hinzufügen",
diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json
index 5f0c0c134..202a6bceb 100644
--- a/app/i18n/locales/en.json
+++ b/app/i18n/locales/en.json
@@ -126,8 +126,6 @@
"Black": "Black",
"Block_user": "Block user",
"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",
"By_proceeding_you_are_agreeing": "By proceeding you are agreeing to our",
"Cancel_editing": "Cancel editing",
@@ -408,7 +406,6 @@
"Preferences": "Preferences",
"Preferences_saved": "Preferences saved!",
"Privacy_Policy": " Privacy Policy",
- "Private_Channel": "Private Channel",
"Private": "Private",
"Processing": "Processing...",
"Profile_saved_successfully": "Profile saved successfully!",
@@ -423,7 +420,6 @@
"Reactions": "Reactions",
"Read_External_Permission_Message": "Rocket.Chat needs to access photos, media, and files on your device",
"Read_External_Permission": "Read Media Permission",
- "Read_Only_Channel": "Read Only Channel",
"Read_Only": "Read Only",
"Read_Receipt": "Read Receipt",
"Receive_Group_Mentions": "Receive Group Mentions",
@@ -726,9 +722,6 @@
"Team_not_found": "Team not found",
"Create_Team": "Create Team",
"Team_Name": "Team Name",
- "Private_Team": "Private Team",
- "Read_Only_Team": "Read Only Team",
- "Broadcast_Team": "Broadcast Team",
"creating_team": "creating team",
"team-name-already-exists": "A team with that name already exists",
"Add_Channel_to_Team": "Add Channel to Team",
@@ -844,6 +837,25 @@
"totp-invalid": "Code or password invalid",
"Close_Chat": "Close Chat",
"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",
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the Also send to channel behavior"
}
\ No newline at end of file
diff --git a/app/i18n/locales/es-ES.json b/app/i18n/locales/es-ES.json
index 81fa6194e..2a3e5bcf1 100644
--- a/app/i18n/locales/es-ES.json
+++ b/app/i18n/locales/es-ES.json
@@ -108,8 +108,7 @@
"Back": "Volver",
"Black": "Negro",
"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_Channel": "Canal de Transmisión",
+ "Broadcast_hint": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.",
"Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Al proceder estarás de acuerdo",
"Cancel_editing": "Cancelar edición",
@@ -273,7 +272,6 @@
"Preferences": "Preferencias",
"Preferences_saved": "¡Preferencias guardadas!",
"Privacy_Policy": "Política de privacidad",
- "Private_Channel": "Canal privado",
"Private": "Privado",
"Processing": "Procesando...",
"Profile_saved_successfully": "¡Perfil guardado correctamente!",
@@ -286,7 +284,6 @@
"Reactions_are_disabled": "Las reacciones están desactivadas",
"Reactions_are_enabled": "Las reacciones están activadas",
"Reactions": "Reacciones",
- "Read_Only_Channel": "Canal de sólo lectura",
"Read_Only": "Sólo lectura ",
"Read_Receipt": "Comprobante de lectura",
"Receive_Group_Mentions": "Recibir menciones de grupo",
diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json
index 62e128f49..b30d3ee5d 100644
--- a/app/i18n/locales/fr.json
+++ b/app/i18n/locales/fr.json
@@ -119,8 +119,7 @@
"Black": "Noir",
"Block_user": "Bloquer l'utilisateur",
"Browser": "Navigateur",
- "Broadcast_channel_Description": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.",
- "Broadcast_Channel": "Canal de diffusion",
+ "Broadcast_hint": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.",
"Busy": "Occupé",
"By_proceeding_you_are_agreeing": "En poursuivant, vous acceptez nos",
"Cancel_editing": "Annuler la modification",
@@ -398,7 +397,6 @@
"Preferences": "Préférences",
"Preferences_saved": "Préférences sauvegardées !",
"Privacy_Policy": " Politique de confidentialité",
- "Private_Channel": "Canal privé",
"Private": "Privé",
"Processing": "Traitement...",
"Profile_saved_successfully": "Profil enregistré avec succès !",
@@ -413,7 +411,6 @@
"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": "Permission de lecture des fichiers",
- "Read_Only_Channel": "Canal en lecture seule",
"Read_Only": "Lecture seule",
"Read_Receipt": "Accusé de réception",
"Receive_Group_Mentions": "Recevoir des mentions de groupe",
@@ -715,9 +712,6 @@
"Team_not_found": "Equipe non trouvée",
"Create_Team": "Créer une é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",
"team-name-already-exists": "Une équipe portant ce nom existe déjà",
"Add_Channel_to_Team": "Ajouter un canal à l'équipe",
diff --git a/app/i18n/locales/it.json b/app/i18n/locales/it.json
index 9617911ad..9792c34e8 100644
--- a/app/i18n/locales/it.json
+++ b/app/i18n/locales/it.json
@@ -115,8 +115,7 @@
"Black": "Nero",
"Block_user": "Blocca utente",
"Browser": "Browser",
- "Broadcast_channel_Description": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere",
- "Broadcast_Channel": "Canale broadcast",
+ "Broadcast_hint": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere",
"Busy": "Occupato",
"By_proceeding_you_are_agreeing": "Procedendo accetti i nostri",
"Cancel_editing": "Annulla modifica",
@@ -383,7 +382,6 @@
"Preferences": "Impostazioni",
"Preferences_saved": "Impostazioni salvate!",
"Privacy_Policy": " Privacy Policy",
- "Private_Channel": "Canale privato",
"Private": "Privato",
"Processing": "Elaborazione...",
"Profile_saved_successfully": "Profilo salvato correttamente!",
@@ -398,7 +396,6 @@
"Reactions": "Reazioni",
"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_Only_Channel": "Canale in sola lettura",
"Read_Only": "Sola lettura",
"Read_Receipt": "Conferma di lettura",
"Receive_Group_Mentions": "Ricevi menzioni di gruppo",
diff --git a/app/i18n/locales/ja.json b/app/i18n/locales/ja.json
index 1367a4be5..53c22c5cf 100644
--- a/app/i18n/locales/ja.json
+++ b/app/i18n/locales/ja.json
@@ -119,8 +119,7 @@
"Black": "ブラック",
"Block_user": "ブロックしたユーザー",
"Browser": "ブラウザ",
- "Broadcast_channel_Description": "許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます",
- "Broadcast_Channel": "配信チャンネル",
+ "Broadcast_hint": "許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます",
"Busy": "取り込み中",
"By_proceeding_you_are_agreeing": "続行することにより、私達を承認します",
"Cancel_editing": "編集をキャンセル",
@@ -371,7 +370,6 @@
"Preferences": "設定",
"Preferences_saved": "設定が保存されました。",
"Privacy_Policy": " プライバシーポリシー",
- "Private_Channel": "プライベートチャンネル",
"Private": "プライベート",
"Processing": "処理中...",
"Profile_saved_successfully": "プロフィールが保存されました!",
@@ -384,7 +382,6 @@
"Reactions_are_disabled": "リアクションは無効化されています",
"Reactions_are_enabled": "リアクションは有効化されています",
"Reactions": "リアクション",
- "Read_Only_Channel": "読み取り専用チャンネル",
"Read_Only": "読み取り専用",
"Read_Receipt": "レシートを見る",
"Receive_Group_Mentions": "グループの通知を受け取る",
diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json
index 141638baa..110c743fe 100644
--- a/app/i18n/locales/nl.json
+++ b/app/i18n/locales/nl.json
@@ -119,8 +119,7 @@
"Black": "Zwart",
"Block_user": "Blokkeer gebruiker",
"Browser": "Browser",
- "Broadcast_channel_Description": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden",
- "Broadcast_Channel": "Uitzendkanaal",
+ "Broadcast_hint": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden",
"Busy": "Bezig",
"By_proceeding_you_are_agreeing": "Door verder te gaan ga je akkoord met onze",
"Cancel_editing": "Bewerken annuleren",
@@ -398,7 +397,6 @@
"Preferences": "Voorkeuren",
"Preferences_saved": "Voorkeuren opgeslagen!",
"Privacy_Policy": " Privacybeleid",
- "Private_Channel": "Privékanaal",
"Private": "Privé",
"Processing": "Verwerking...",
"Profile_saved_successfully": "Profiel succesvol opgeslagen!",
@@ -413,7 +411,6 @@
"Reactions": "Reacties",
"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_Only_Channel": "Alleen-lezen kanaal",
"Read_Only": "Alleen lezen",
"Read_Receipt": "Leesbevestiging",
"Receive_Group_Mentions": "Groepsvermeldingen ontvangen",
@@ -715,9 +712,6 @@
"Team_not_found": "Team niet gevonden",
"Create_Team": "Team aanmaken",
"Team_Name": "Teamnaam",
- "Private_Team": "Privé team",
- "Read_Only_Team": "Alleen-lezen team",
- "Broadcast_Team": "Broadcast team",
"creating_team": "team maken",
"team-name-already-exists": "Er bestaat al een team met die naam",
"Add_Channel_to_Team": "Kanaal toevoegen aan team",
diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json
index 694aef0d6..32c5617f1 100644
--- a/app/i18n/locales/pt-BR.json
+++ b/app/i18n/locales/pt-BR.json
@@ -121,8 +121,6 @@
"Black": "Preto",
"Block_user": "Bloquear usuário",
"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",
"By_proceeding_you_are_agreeing": "Ao prosseguir você está aceitando",
"Cancel_editing": "Cancelar edição",
@@ -384,7 +382,6 @@
"Preferences": "Preferências",
"Preferences_saved": "Preferências salvas!",
"Privacy_Policy": " Política de Privacidade",
- "Private_Channel": "Canal Privado",
"Private": "Privado",
"Processing": "Processando...",
"Profile_saved_successfully": "Perfil salvo com sucesso!",
@@ -399,7 +396,6 @@
"Reactions": "Reações",
"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_Only_Channel": "Canal Somente Leitura",
"Read_Only": "Somente Leitura",
"Read_Receipt": "Lida por",
"Receive_Group_Mentions": "Receber menções de grupo",
@@ -683,7 +679,6 @@
"Teams": "Times",
"No_team_channels_found": "Nenhum canal encontrado",
"Team_not_found": "Time não encontrado",
- "Private_Team": "Equipe Privada",
"Add_Channel_to_Team": "Adicionar Canal ao Time",
"Left_The_Team_Successfully": "Saiu do time com sucesso",
"Create_New": "Criar",
@@ -797,6 +792,25 @@
"totp-invalid": "Código ou senha inválida",
"Close_Chat": "Fechar Conversa",
"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",
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal"
}
\ No newline at end of file
diff --git a/app/i18n/locales/pt-PT.json b/app/i18n/locales/pt-PT.json
index 9509842ec..fd6f3d5be 100644
--- a/app/i18n/locales/pt-PT.json
+++ b/app/i18n/locales/pt-PT.json
@@ -118,8 +118,7 @@
"Black": "Preto",
"Block_user": "Bloquear utilizador",
"Browser": "Navegador",
- "Broadcast_channel_Description": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder",
- "Broadcast_Channel": "Canal de Transmissão",
+ "Broadcast_hint": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder",
"Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Ao prosseguir você concorda com o(s) nosso(s)",
"Cancel_editing": "Cancelar edição",
@@ -390,7 +389,6 @@
"Preferences": "Preferências",
"Preferences_saved": "Preferências guardadas!",
"Privacy_Policy": " Política de Privacidade",
- "Private_Channel": "Canal Privado",
"Private": "Privado",
"Processing": "A processar...",
"Profile_saved_successfully": "Perfil actualizado com sucesso!",
@@ -405,7 +403,6 @@
"Reactions": "Reacções",
"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_Only_Channel": "Canal só de leitura",
"Read_Only": "Só de Leitura",
"Read_Receipt": "Recibos de leitura",
"Register": "Registar",
diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json
index adb8923f6..01bbc8217 100644
--- a/app/i18n/locales/ru.json
+++ b/app/i18n/locales/ru.json
@@ -119,8 +119,7 @@
"Black": "Черный",
"Block_user": "Блокировать пользователя",
"Browser": "Браузер",
- "Broadcast_channel_Description": "Только авторизованные пользователи могут писать новые сообщения, но другие пользователи смогут ответить",
- "Broadcast_Channel": "Широковещательный канал",
+ "Broadcast_hint": "Только авторизованные пользователи могут писать новые сообщения, но другие пользователи смогут ответить",
"Busy": "Занят",
"By_proceeding_you_are_agreeing": "Продолжая, вы соглашаетесь с нашими",
"Cancel_editing": "Отменить правку",
@@ -394,7 +393,6 @@
"Preferences": "Настройки",
"Preferences_saved": "Настройки сохранены!",
"Privacy_Policy": " Политика конфиденциальности",
- "Private_Channel": "Приватный канал",
"Private": "Приватный",
"Processing": "Обработка...",
"Profile_saved_successfully": "Профиль успешно сохранен!",
@@ -409,7 +407,6 @@
"Reactions": "Реакции",
"Read_External_Permission_Message": "Rocket.Chat необходим доступ к фотографиям, медиа и другим файлам на вашем устройстве",
"Read_External_Permission": "Разрешение на Чтение Медиа",
- "Read_Only_Channel": "Канал только для чтения",
"Read_Only": "Только для чтения",
"Read_Receipt": "Уведомление о прочтении",
"Receive_Group_Mentions": "Получать групповые уведомления",
@@ -706,9 +703,6 @@
"Team_not_found": "Команда не найдена",
"Create_Team": "Создать Команду",
"Team_Name": "Имя Команды",
- "Private_Team": "Приватная Команда",
- "Read_Only_Team": "Команда только для чтения",
- "Broadcast_Team": "Широковещательная Команда",
"creating_team": "создание Команды",
"team-name-already-exists": "Команда с таким названием уже существует",
"Add_Channel_to_Team": "Добавить канал в Команду",
diff --git a/app/i18n/locales/tr.json b/app/i18n/locales/tr.json
index 092dbfda6..e8f26d3bf 100644
--- a/app/i18n/locales/tr.json
+++ b/app/i18n/locales/tr.json
@@ -115,8 +115,7 @@
"Black": "Koyu",
"Block_user": "Kullanıcıyı engelle",
"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_Channel": "Kanala Yayınla",
+ "Broadcast_hint": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir",
"Busy": "Meşgul",
"By_proceeding_you_are_agreeing": "Devam ederek kabul ediyorsunuz: ",
"Cancel_editing": "Düzenlemeyi iptal et",
@@ -384,7 +383,6 @@
"Preferences": "Tercihler",
"Preferences_saved": "Tercihler kaydedildi!",
"Privacy_Policy": " Privacy Policy",
- "Private_Channel": "Özel Kanal",
"Private": "Özel",
"Processing": "İşleniyor...",
"Profile_saved_successfully": "Profil başarıyla kaydedildi!",
@@ -399,7 +397,6 @@
"Reactions": "Tepkiler",
"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_Only_Channel": "Yazma Kısıtlı Kanal",
"Read_Only": "Yazma Kısıtlı",
"Read_Receipt": "Okundu Bilgisi",
"Receive_Group_Mentions": "Grup Bahsetmelerini Al",
diff --git a/app/i18n/locales/zh-CN.json b/app/i18n/locales/zh-CN.json
index 119dfdf6c..6e6065347 100644
--- a/app/i18n/locales/zh-CN.json
+++ b/app/i18n/locales/zh-CN.json
@@ -115,8 +115,7 @@
"Black": "黑色",
"Block_user": "屏蔽此用户",
"Browser": "浏览器",
- "Broadcast_channel_Description": "只有经过授权的用户才能写新信息,但其他用户可以回复",
- "Broadcast_Channel": "广播频道",
+ "Broadcast_hint": "只有经过授权的用户才能写新信息,但其他用户可以回复",
"Busy": "忙碌",
"By_proceeding_you_are_agreeing": "继续操作,请同意我们的",
"Cancel_editing": "取消编辑",
@@ -381,7 +380,6 @@
"Preferences": "偏好设置",
"Preferences_saved": "偏好已保存!",
"Privacy_Policy": "隐私政策",
- "Private_Channel": "私人频道",
"Private": "私有的",
"Processing": "处理中",
"Profile_saved_successfully": "个人资料保存成功!",
@@ -396,7 +394,6 @@
"Reactions": "表情貼",
"Read_External_Permission_Message": "Rocket.Chat 需要存取您装置上的相片、多媒体及文件",
"Read_External_Permission": "读取媒体权限",
- "Read_Only_Channel": "只读频道",
"Read_Only": "只读",
"Read_Receipt": "查看已读人员",
"Receive_Group_Mentions": "接收群组提及",
diff --git a/app/i18n/locales/zh-TW.json b/app/i18n/locales/zh-TW.json
index 4e8827ed6..bc9ea3b5c 100644
--- a/app/i18n/locales/zh-TW.json
+++ b/app/i18n/locales/zh-TW.json
@@ -116,8 +116,7 @@
"Black": "黑色",
"Block_user": "封鎖此用戶",
"Browser": "瀏覽器",
- "Broadcast_channel_Description": "只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆",
- "Broadcast_Channel": "廣播頻道",
+ "Broadcast_hint": "只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆",
"Busy": "忙碌",
"By_proceeding_you_are_agreeing": "若要繼續操作,請同意我們的",
"Cancel_editing": "取消編輯",
@@ -383,7 +382,6 @@
"Preferences": "偏好設定",
"Preferences_saved": "偏好設定已被儲存!",
"Privacy_Policy": "隱私政策",
- "Private_Channel": "私人頻道",
"Private": "私有的",
"Processing": "處理中",
"Profile_saved_successfully": "個人資料儲存成功!",
@@ -398,7 +396,6 @@
"Reactions": "表情貼",
"Read_External_Permission_Message": "Rocket.Chat 需要存取您裝置上的相片、多媒體及檔案",
"Read_External_Permission": "讀取媒體權限",
- "Read_Only_Channel": "唯讀頻道",
"Read_Only": "唯讀",
"Read_Receipt": "查看已讀人員",
"Receive_Group_Mentions": "接收群組提及",
diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx
index d5938cb58..4c7007bd8 100644
--- a/app/stacks/InsideStack.tsx
+++ b/app/stacks/InsideStack.tsx
@@ -123,7 +123,7 @@ const ChatsStackNavigator = () => {
options={ThreadMessagesView.navigationOptions}
/>
-
+
{
-
+
-
+
);
diff --git a/app/stacks/MasterDetailStack/index.tsx b/app/stacks/MasterDetailStack/index.tsx
index 80b2637db..37fb43bd9 100644
--- a/app/stacks/MasterDetailStack/index.tsx
+++ b/app/stacks/MasterDetailStack/index.tsx
@@ -187,9 +187,9 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
-
+
-
+
diff --git a/app/views/CreateChannelView.tsx b/app/views/CreateChannelView.tsx
deleted file mode 100644
index 9c09c2d2b..000000000
--- a/app/views/CreateChannelView.tsx
+++ /dev/null
@@ -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 {
- 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 {
- 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 && (
-
-
-
- )
- });
- };
-
- 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 (
-
- {I18n.t(label)}
-
-
- );
- };
-
- 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 }) => (
- this.removeUser(item)}
- testID={`create-channel-view-item-${item.name}`}
- icon='check'
- />
- );
-
- renderInvitedList = () => {
- const { users, theme } = this.props;
-
- return (
- 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 (
-
-
-
-
-
-
-
- {this.renderType()}
-
- {this.renderReadOnly()}
-
- {this.renderEncrypted()}
-
- {this.renderBroadcast()}
-
-
- {I18n.t('Invite')}
-
- {userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}
-
-
- {this.renderInvitedList()}
-
-
-
- );
- }
-}
-
-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));
diff --git a/app/views/CreateChannelView/RoomSettings/SwitchItem.stories.tsx b/app/views/CreateChannelView/RoomSettings/SwitchItem.stories.tsx
new file mode 100644
index 000000000..09aa2bc2c
--- /dev/null
+++ b/app/views/CreateChannelView/RoomSettings/SwitchItem.stories.tsx
@@ -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 = () => (
+ <>
+
+ testSwitch.onValueChange()}
+ value={testSwitch.value}
+ />
+
+ >
+);
diff --git a/app/views/CreateChannelView/RoomSettings/SwitchItem.test.tsx b/app/views/CreateChannelView/RoomSettings/SwitchItem.test.tsx
new file mode 100644
index 000000000..fa1fc6846
--- /dev/null
+++ b/app/views/CreateChannelView/RoomSettings/SwitchItem.test.tsx
@@ -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) => (
+
+
+
+);
+
+describe('SwitchItemEncrypted', () => {
+ it('should not render the Encrypted Switch component', async () => {
+ const { findByTestId } = render(
+ 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(
+ 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(
+ testSwitch.onValueChange(value)}
+ value={testSwitch.value}
+ />
+ );
+ const component = await findByTestId(testSwitch.testLabelID);
+ expect(component.props.children).toBe(i18n.t(testSwitch.hint));
+ });
+});
diff --git a/app/views/CreateChannelView/RoomSettings/SwitchItem.tsx b/app/views/CreateChannelView/RoomSettings/SwitchItem.tsx
new file mode 100644
index 000000000..4e8b548e3
--- /dev/null
+++ b/app/views/CreateChannelView/RoomSettings/SwitchItem.tsx
@@ -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 (
+
+
+ {I18n.t(label)}
+
+ {I18n.t(hint)}
+
+
+
+
+ );
+};
diff --git a/app/views/CreateChannelView/RoomSettings/SwitchItemEncrypted.test.tsx b/app/views/CreateChannelView/RoomSettings/SwitchItemEncrypted.test.tsx
new file mode 100644
index 000000000..f6705b505
--- /dev/null
+++ b/app/views/CreateChannelView/RoomSettings/SwitchItemEncrypted.test.tsx
@@ -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) => (
+
+
+
+);
+
+describe('SwitchItemEncrypted', () => {
+ it('should not render the Encrypted Switch component', async () => {
+ const { findByTestId } = render(
+ 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(
+ 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(
+ 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(
+ 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(
+ testEncrypted.onValueChangeEncrypted(value)}
+ type={testEncrypted.type}
+ />
+ );
+ const component = await findByTestId(testEncrypted.testLabelID);
+ expect(component.props.children).toBe(i18n.t('Team_hint_encrypted'));
+ });
+});
diff --git a/app/views/CreateChannelView/RoomSettings/SwitchItemEncrypted.tsx b/app/views/CreateChannelView/RoomSettings/SwitchItemEncrypted.tsx
new file mode 100644
index 000000000..9cc277c65
--- /dev/null
+++ b/app/views/CreateChannelView/RoomSettings/SwitchItemEncrypted.tsx
@@ -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 (
+
+ );
+};
diff --git a/app/views/CreateChannelView/RoomSettings/SwitchItemReadOnly.tsx b/app/views/CreateChannelView/RoomSettings/SwitchItemReadOnly.tsx
new file mode 100644
index 000000000..7711879cc
--- /dev/null
+++ b/app/views/CreateChannelView/RoomSettings/SwitchItemReadOnly.tsx
@@ -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 (
+
+ );
+};
diff --git a/app/views/CreateChannelView/RoomSettings/SwitchItemType.tsx b/app/views/CreateChannelView/RoomSettings/SwitchItemType.tsx
new file mode 100644
index 000000000..6299dec97
--- /dev/null
+++ b/app/views/CreateChannelView/RoomSettings/SwitchItemType.tsx
@@ -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 (
+
+ );
+};
diff --git a/app/views/CreateChannelView/RoomSettings/index.tsx b/app/views/CreateChannelView/RoomSettings/index.tsx
new file mode 100644
index 000000000..5d27ca1e0
--- /dev/null
+++ b/app/views/CreateChannelView/RoomSettings/index.tsx
@@ -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 }) => {
+ 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 (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/app/views/CreateChannelView/index.tsx b/app/views/CreateChannelView/index.tsx
new file mode 100644
index 000000000..be02e0d9f
--- /dev/null
+++ b/app/views/CreateChannelView/index.tsx
@@ -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({
+ defaultValues: { channelName: '', broadcast: false, encrypted: false, readOnly: false, type: false }
+ });
+
+ const navigation = useNavigation>();
+ const { params } = useRoute>();
+ 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 (
+
+
+
+
+
+
+
+
+ {users.length > 0 ? (
+ <>
+
+
+ {I18n.t('N_Selected_members', { n: users.length })}
+
+
+ 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 (
+ removeUser(item)}
+ testID={`create-channel-view-item-${item.name}`}
+ />
+ );
+ }}
+ keyboardShouldPersistTaps='always'
+ horizontal
+ />
+ >
+ ) : null}
+
+
+
+
+ );
+};
+
+export default CreateChannelView;
diff --git a/app/views/NewMessageView.tsx b/app/views/NewMessageView.tsx
deleted file mode 100644
index 2b6f65eb6..000000000
--- a/app/views/NewMessageView.tsx
+++ /dev/null
@@ -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 {
- maxUsers: number;
- isMasterDetail: boolean;
- serverVersion: string;
- createTeamPermission?: string[];
- createDirectMessagePermission?: string[];
- createPublicChannelPermission?: string[];
- createPrivateChannelPermission?: string[];
- createDiscussionPermission?: string[];
-}
-
-class NewMessageView extends React.Component {
- static navigationOptions = ({ navigation }: INewMessageViewProps): StackNavigationOptions => ({
- headerLeft: () => ,
- 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 (
-
-
-
- {title}
-
-
- );
- };
-
- 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 (
-
- this.onSearchChangeText(text)} testID='new-message-view-search' />
-
- {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}
-
-
- );
- };
-
- 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 (
- this.goRoom(itemModel)}
- testID={`new-message-view-item-${item.name}`}
- style={style}
- />
- );
- };
-
- renderList = () => {
- const { search, chats } = this.state;
- const { theme } = this.props;
- return (
- 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 (
-
-
- {this.renderList()}
-
- );
- }
-}
-
-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));
diff --git a/app/views/NewMessageView/ButtonCreate.tsx b/app/views/NewMessageView/ButtonCreate.tsx
new file mode 100644
index 000000000..f3dc7d2d2
--- /dev/null
+++ b/app/views/NewMessageView/ButtonCreate.tsx
@@ -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 (
+ <>
+ }
+ right={() => }
+ title={title}
+ />
+
+ >
+ );
+};
+
+export default ButtonCreate;
diff --git a/app/views/NewMessageView/HeaderNewMessage.tsx b/app/views/NewMessageView/HeaderNewMessage.tsx
new file mode 100644
index 000000000..458c745dc
--- /dev/null
+++ b/app/views/NewMessageView/HeaderNewMessage.tsx
@@ -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>();
+ 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 (
+ <>
+
+
+ {createPublicChannelPermission || createPrivateChannelPermission ? (
+
+ ) : null}
+ {compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && createTeamPermission ? (
+
+ ) : null}
+ {maxUsers > 2 && createDirectMessagePermission ? (
+
+ ) : null}
+ {createDiscussionPermission ? (
+
+ ) : null}
+
+
+ onChangeText(text)} testID='new-message-view-search' />
+ >
+ );
+};
+
+export default HeaderNewMessage;
diff --git a/app/views/NewMessageView/index.tsx b/app/views/NewMessageView/index.tsx
new file mode 100644
index 000000000..8f05d5809
--- /dev/null
+++ b/app/views/NewMessageView/index.tsx
@@ -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([]);
+ const [search, setSearch] = useState([]);
+
+ const { colors } = useTheme();
+
+ const navigation = useNavigation>();
+
+ 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: () => ,
+ 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 (
+
+
+ 0 ? search : chats}
+ keyExtractor={item => item._id || item.rid}
+ ListHeaderComponent={}
+ renderItem={({ item }) => {
+ const itemSearch = item as ISearch;
+ const itemModel = item as TSubscriptionModel;
+
+ return (
+ goRoom(itemModel)}
+ testID={`new-message-view-item-${item.name}`}
+ />
+ );
+ }}
+ ItemSeparatorComponent={List.Separator}
+ ListFooterComponent={List.Separator}
+ contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
+ keyboardShouldPersistTaps='always'
+ />
+
+ );
+};
+
+export default NewMessageView;
diff --git a/app/views/RoomInfoEditView/index.tsx b/app/views/RoomInfoEditView/index.tsx
index 27f685141..a55bc2a3a 100644
--- a/app/views/RoomInfoEditView/index.tsx
+++ b/app/views/RoomInfoEditView/index.tsx
@@ -705,7 +705,7 @@ class RoomInfoEditView extends React.Component{I18n.t('Broadcast_Channel')},
+ {I18n.t('Broadcast')},
]
: null}
diff --git a/app/views/RoomInfoView/Channel.tsx b/app/views/RoomInfoView/Channel.tsx
index c82b7f032..8467512c3 100644
--- a/app/views/RoomInfoView/Channel.tsx
+++ b/app/views/RoomInfoView/Channel.tsx
@@ -24,8 +24,8 @@ const Channel = ({ room }: { room: ISubscription }) => {
testID='room-info-view-announcement'
/>
>
diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts
index f7576fb3e..696de2c56 100644
--- a/app/views/RoomMembersView/helpers.ts
+++ b/app/views/RoomMembersView/helpers.ts
@@ -120,6 +120,7 @@ const removeFromTeam = async (
updateState({
members: newMembers
});
+ appNavigation.navigate('RoomMembersView', { room });
}
} catch (e: any) {
log(e);
diff --git a/app/views/SelectedUsersView.tsx b/app/views/SelectedUsersView.tsx
deleted file mode 100644
index a651a7178..000000000
--- a/app/views/SelectedUsersView.tsx
+++ /dev/null
@@ -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 {
- users: ISelectedUser[];
- loading: boolean;
- user: IUser;
- baseUrl: string;
-}
-
-class SelectedUsersView extends React.Component {
- 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) && (
-
-
-
- )
- };
- 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 (
-
- this.onSearchChangeText(text)} testID='select-users-view-search' />
- {this.renderSelected()}
-
- );
- };
-
- 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 (
- item._id}
- style={[sharedStyles.separatorTop, { borderColor: themes[theme].separatorColor }]}
- contentContainerStyle={{ marginVertical: 5 }}
- renderItem={this.renderSelectedItem}
- keyboardShouldPersistTaps='always'
- horizontal
- />
- );
- };
-
- renderSelectedItem = ({ item }: { item: ISelectedUser }) => (
- 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 (
- 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 (
- item._id}
- renderItem={this.renderItem}
- ItemSeparatorComponent={List.Separator}
- ListHeaderComponent={this.renderHeader}
- contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
- keyboardShouldPersistTaps='always'
- />
- );
- };
-
- render() {
- return (
-
-
- {this.renderList()}
-
- );
- }
-}
-
-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));
diff --git a/app/views/SelectedUsersView/Header.tsx b/app/views/SelectedUsersView/Header.tsx
new file mode 100644
index 000000000..6d2824a35
--- /dev/null
+++ b/app/views/SelectedUsersView/Header.tsx
@@ -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();
+ const { theme } = useTheme();
+ const { users } = useAppSelector(state => ({
+ users: state.selectedUsers.users
+ }));
+
+ const onContentSizeChange = () => flatlist?.current?.scrollToEnd({ animated: true });
+
+ return (
+
+ onChangeText(text)} testID='select-users-view-search' />
+ {users.length === 0 ? null : (
+
+
+ {I18n.t('N_Selected_members', { n: users.length })}
+
+ (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 (
+ onPressItem(item)} testID={`selected-user-${item.name}`} />
+ );
+ }}
+ keyboardShouldPersistTaps='always'
+ contentContainerStyle={styles.contentContainerList}
+ horizontal
+ />
+
+ )}
+
+ );
+};
+
+export default Header;
diff --git a/app/views/SelectedUsersView/index.tsx b/app/views/SelectedUsersView/index.tsx
new file mode 100644
index 000000000..3a0696462
--- /dev/null
+++ b/app/views/SelectedUsersView/index.tsx
@@ -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;
+type TNavigation = StackNavigationProp;
+
+type TSearchItem = ISearch | ISearchLocal;
+
+const SelectedUsersView = () => {
+ const [chats, setChats] = useState([]);
+ const [search, setSearch] = useState([]);
+
+ const { maxUsers, showButton, title, buttonText, nextAction } = useRoute().params;
+ const navigation = useNavigation();
+
+ 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)) && (
+
+ 0 ? buttonTextHeader : I18n.t('Skip')}
+ onPress={nextActionHeader}
+ testID='selected-users-view-submit'
+ />
+
+ )
+ };
+ 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 (
+
+
+ item._id}
+ renderItem={({ item }) => {
+ const name = useRealName && item.fname ? item.fname : item.name;
+ const username = item.search ? (item.username as string) : item.name;
+ return (
+ _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={}
+ ListHeaderComponent={}
+ contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
+ keyboardShouldPersistTaps='always'
+ />
+
+ );
+};
+
+export default SelectedUsersView;
diff --git a/e2e/helpers/app.js b/e2e/helpers/app.js
index 96fe34fd6..50e2d7031 100644
--- a/e2e/helpers/app.js
+++ b/e2e/helpers/app.js
@@ -97,9 +97,10 @@ async function mockMessage(message, isThread = false) {
await element(by.id(input)).replaceText(`${data.random}${message}`);
await sleep(300);
await element(by.id('messagebox-send-message')).tap();
+ await sleep(500);
await waitFor(element(by[textMatcher](`${data.random}${message}`)))
.toExist()
- .withTimeout(60000);
+ .withTimeout(10000);
await element(by[textMatcher](`${data.random}${message}`))
.atIndex(0)
.tap();
diff --git a/e2e/tests/assorted/02-broadcast.spec.js b/e2e/tests/assorted/02-broadcast.spec.js
index ef0844074..06ebc9d43 100644
--- a/e2e/tests/assorted/02-broadcast.spec.js
+++ b/e2e/tests/assorted/02-broadcast.spec.js
@@ -57,7 +57,7 @@ describe('Broadcast room', () => {
await waitFor(element(by.id('room-info-view')))
.toBeVisible()
.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 waitFor(element(by.id('room-actions-view')))
.toBeVisible()
diff --git a/e2e/tests/assorted/03-profile.spec.js b/e2e/tests/assorted/03-profile.spec.js
index 0b5287da7..facec3a4e 100644
--- a/e2e/tests/assorted/03-profile.spec.js
+++ b/e2e/tests/assorted/03-profile.spec.js
@@ -10,7 +10,7 @@ async function waitForToast() {
// await expect(element(by.id('toast'))).toBeVisible();
// await waitFor(element(by.id('toast'))).not.toBeNotVisible().withTimeout(1000);
// await expect(element(by.id('toast'))).not.toBeVisible();
- await sleep(300);
+ await sleep(600);
}
describe('Profile screen', () => {
diff --git a/e2e/tests/room/04-discussion.spec.js b/e2e/tests/room/04-discussion.spec.js
index f4ac2fc9d..6125ecf6e 100644
--- a/e2e/tests/room/04-discussion.spec.js
+++ b/e2e/tests/room/04-discussion.spec.js
@@ -29,7 +29,7 @@ describe('Discussion', () => {
await waitFor(element(by.id('new-message-view')))
.toExist()
.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')))
.toExist()
.withTimeout(60000);
diff --git a/e2e/tests/room/06-createdmgroup.spec.js b/e2e/tests/room/06-createdmgroup.spec.js
index cd239f484..785943bd2 100644
--- a/e2e/tests/room/06-createdmgroup.spec.js
+++ b/e2e/tests/room/06-createdmgroup.spec.js
@@ -34,7 +34,7 @@ describe('Group DM', () => {
describe('Usage', () => {
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 () => {
diff --git a/e2e/tests/room/08-roominfo.spec.js b/e2e/tests/room/08-roominfo.spec.js
index 0e26158c4..437b79d2a 100644
--- a/e2e/tests/room/08-roominfo.spec.js
+++ b/e2e/tests/room/08-roominfo.spec.js
@@ -175,7 +175,7 @@ describe('Room info screen', () => {
.toExist()
.withTimeout(2000);
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 waitForToast();
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-password')).replaceText('abc');
await element(by.id('room-info-edit-view-t')).tap();
- await swipe('up');
- 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-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-react-when-ro')).tap();
await swipe('up');
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 swipe('down');
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-react-when-ro'))).toBeNotVisible();
+ await expect(element(by.id('room-info-edit-view-ro'))).toHaveToggleValue(true);
+ await expect(element(by.id('room-info-edit-view-react-when-ro'))).toHaveToggleValue(false);
await swipe('down');
});
it('should change room description', async () => {
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 waitForToast();
await tapBack();
@@ -227,7 +227,7 @@ describe('Room info screen', () => {
.toExist()
.withTimeout(2000);
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 waitForToast();
await tapBack();
@@ -246,7 +246,7 @@ describe('Room info screen', () => {
.toExist()
.withTimeout(2000);
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 waitForToast();
await tapBack();
@@ -265,7 +265,7 @@ describe('Room info screen', () => {
.toExist()
.withTimeout(2000);
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 waitForToast();
});
@@ -273,12 +273,12 @@ describe('Room info screen', () => {
it('should change room type', async () => {
await swipe('down');
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 waitForToast();
await swipe('down');
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 waitForToast();
});
@@ -298,7 +298,7 @@ describe('Room info screen', () => {
});
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 waitFor(element(by[textMatcher]('Yes, delete it!')))
.toExist()
diff --git a/e2e/tests/team/01-createteam.spec.js b/e2e/tests/team/01-createteam.spec.js
index 27993db41..1f9560243 100644
--- a/e2e/tests/team/01-createteam.spec.js
+++ b/e2e/tests/team/01-createteam.spec.js
@@ -15,6 +15,9 @@ describe('Create team screen', () => {
describe('New Message', () => {
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();
});
diff --git a/package.json b/package.json
index 4bd05d818..01d501cb4 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@bugsnag/react-native": "^7.10.5",
"@codler/react-native-keyboard-aware-scroll-view": "^2.0.1",
"@gorhom/bottom-sheet": "^4.3.1",
+ "@hookform/resolvers": "^2.9.7",
"@nozbe/watermelondb": "0.23.0",
"@react-native-async-storage/async-storage": "^1.17.9",
"@react-native-clipboard/clipboard": "^1.8.5",
@@ -79,6 +80,7 @@
"pretty-bytes": "5.6.0",
"prop-types": "15.7.2",
"react": "17.0.2",
+ "react-hook-form": "^7.34.2",
"react-native": "RocketChat/react-native#0.68.2",
"react-native-animatable": "^1.3.3",
"react-native-background-timer": "2.4.1",
@@ -136,7 +138,8 @@
"uri-js": "^4.4.1",
"url-parse": "1.5.10",
"use-deep-compare-effect": "1.6.1",
- "xregexp": "5.0.2"
+ "xregexp": "5.0.2",
+ "yup": "^0.32.11"
},
"resolutions": {
"ua-parser-js": "^1.0.2",
diff --git a/yarn.lock b/yarn.lock
index e592fe5a9..260f0069b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3005,7 +3005,7 @@
dependencies:
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"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
@@ -3944,6 +3944,11 @@
dependencies:
"@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":
version "0.5.0"
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"
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@*":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
@@ -14091,6 +14101,11 @@ locate-path@^6.0.0:
dependencies:
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:
version "4.2.0"
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"
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:
version "3.1.23"
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"
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:
version "5.5.0"
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"
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:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -19507,6 +19537,11 @@ topo@2.x.x:
dependencies:
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:
version "0.0.3"
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"
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:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"