[IMPROVE] Redesign create room flow (#4381)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
9091fabcc6
commit
360ecc1f43
|
@ -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"),
|
||||
|
|
|
@ -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\\"]}]}]}]}]}"`;
|
|
@ -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}]}]}"`;
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import Chip, { IChip } from './index';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'flex-start',
|
||||
padding: 16
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
title: 'Chip'
|
||||
};
|
||||
|
||||
const ChipWrapped = ({ avatar, text, onPress, testID, style }: IChip) => (
|
||||
<View style={styles.container}>
|
||||
<Chip avatar={avatar} text={text} onPress={onPress} testID={testID} style={style} />
|
||||
</View>
|
||||
);
|
||||
|
||||
export const ChipText = () => <ChipWrapped avatar='rocket.cat' text={'Rocket.Cat'} onPress={() => {}} />;
|
||||
|
||||
export const ChipWithShortText = () => <ChipWrapped avatar='rocket.cat' text={'Short'} onPress={() => {}} />;
|
||||
|
||||
export const ChipWithoutAvatar = () => <ChipWrapped text={'Without Avatar'} onPress={() => {}} />;
|
||||
|
||||
export const ChipWithoutIcon = () => <ChipWrapped avatar='rocket.cat' text='Without Icon' />;
|
||||
|
||||
export const ChipWithoutAvatarAndIcon = () => <ChipWrapped text='Without Avatar and Icon' />;
|
|
@ -0,0 +1,58 @@
|
|||
import React from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import Chip, { IChip } from '.';
|
||||
import { ISelectedUser } from '../../reducers/selectedUsers';
|
||||
import { mockedStore as store } from '../../reducers/mockedStore';
|
||||
|
||||
const onPressMock = jest.fn((item: any) => item);
|
||||
|
||||
const testChip = {
|
||||
testID: 'test-chip-id',
|
||||
item: { fname: 'rocket.chat', name: 'rocket.chat' } as ISelectedUser,
|
||||
onPress: onPressMock
|
||||
};
|
||||
|
||||
const Render = ({ testID, text, avatar, onPress }: IChip) => (
|
||||
<Provider store={store}>
|
||||
<Chip testID={testID} text={text} onPress={onPress} avatar={avatar} />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
describe('Chips', () => {
|
||||
it('should render the Chip component', () => {
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
text={testChip.item.fname}
|
||||
avatar={testChip.item.name}
|
||||
testID={testChip.testID}
|
||||
onPress={() => testChip.onPress(testChip.item)}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(findByTestId(testChip.testID)).toBeTruthy();
|
||||
});
|
||||
it("should not call onPress if it's not passed", async () => {
|
||||
const { findByTestId } = render(<Render text={testChip.item.fname} avatar={testChip.item.name} testID={testChip.testID} />);
|
||||
|
||||
const component = await findByTestId(testChip.testID);
|
||||
fireEvent.press(component);
|
||||
expect(onPressMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it('should tap Chip and return item', async () => {
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
text={testChip.item.fname}
|
||||
avatar={testChip.item.name}
|
||||
testID={testChip.testID}
|
||||
onPress={() => testChip.onPress(testChip.item)}
|
||||
/>
|
||||
);
|
||||
|
||||
const component = await findByTestId(testChip.testID);
|
||||
fireEvent.press(component);
|
||||
expect(onPressMock).toHaveBeenCalled();
|
||||
expect(onPressMock).toHaveReturnedWith(testChip.item);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
import React from 'react';
|
||||
import { Pressable, StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import Avatar from '../Avatar';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
pressable: {
|
||||
paddingHorizontal: 8,
|
||||
marginRight: 8,
|
||||
borderRadius: 2,
|
||||
justifyContent: 'center',
|
||||
maxWidth: 192
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
avatar: {
|
||||
marginRight: 8,
|
||||
marginVertical: 8
|
||||
},
|
||||
textContainer: {
|
||||
marginRight: 8,
|
||||
maxWidth: 120
|
||||
},
|
||||
name: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textMedium
|
||||
}
|
||||
});
|
||||
|
||||
export interface IChip {
|
||||
avatar?: string;
|
||||
text: string;
|
||||
onPress?: Function;
|
||||
testID?: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const Chip = ({ avatar, text, onPress, testID, style }: IChip) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
testID={testID}
|
||||
style={({ pressed }) => [
|
||||
styles.pressable,
|
||||
{
|
||||
backgroundColor: pressed ? colors.bannerBackground : colors.auxiliaryBackground
|
||||
},
|
||||
style
|
||||
]}
|
||||
disabled={!onPress}
|
||||
onPress={() => onPress?.()}
|
||||
android_ripple={{
|
||||
color: colors.bannerBackground
|
||||
}}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
{avatar ? <Avatar text={avatar} size={28} style={styles.avatar} /> : null}
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.name, { color: colors.bodyText }]} numberOfLines={1}>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
{onPress ? <CustomIcon name='close' size={16} color={colors.auxiliaryTintColor} /> : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default Chip;
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
|
||||
import { FormTextInput, IRCTextInputProps } from './FormTextInput';
|
||||
|
||||
interface IControlledFormTextInputProps extends IRCTextInputProps {
|
||||
control: Control<any>;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const ControlledFormTextInput = ({ control, name, ...props }: IControlledFormTextInputProps) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field: { onChange, value } }) => <FormTextInput onChangeText={onChange} value={value} {...props} />}
|
||||
/>
|
||||
);
|
|
@ -1,2 +1,3 @@
|
|||
export * from './TextInput';
|
||||
export * from './FormTextInput';
|
||||
export * from './ControlledFormTextInput';
|
||||
|
|
|
@ -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<ViewStyle>;
|
||||
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 (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
|
@ -65,14 +63,11 @@ const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon }:
|
|||
<View style={[styles.container, styles.button, style]}>
|
||||
<Avatar text={username} size={30} style={styles.avatar} />
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.name, { color: colors.titleText }]} numberOfLines={1}>
|
||||
<Text style={[styles.name, { color: colors.bodyText }]} numberOfLines={1}>
|
||||
{name}
|
||||
</Text>
|
||||
<Text style={[styles.username, { color: colors.auxiliaryText }]} numberOfLines={1}>
|
||||
@{username}
|
||||
</Text>
|
||||
</View>
|
||||
{icon ? <CustomIcon name={icon} size={22} color={colors.actionTintColor} style={styles.icon} /> : null}
|
||||
{icon ? <CustomIcon name={icon} size={22} color={iconColor || colors.actionTintColor} style={styles.icon} /> : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
|
|
|
@ -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": "تلقي إشارات المجموعة",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "グループの通知を受け取る",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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": "Добавить канал в Команду",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "接收群组提及",
|
||||
|
|
|
@ -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": "接收群組提及",
|
||||
|
|
|
@ -123,7 +123,7 @@ const ChatsStackNavigator = () => {
|
|||
options={ThreadMessagesView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen name='TeamChannelsView' component={TeamChannelsView} />
|
||||
<ChatsStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} />
|
||||
<ChatsStack.Screen name='CreateChannelView' component={CreateChannelView} />
|
||||
<ChatsStack.Screen name='AddChannelTeamView' component={AddChannelTeamView} />
|
||||
<ChatsStack.Screen
|
||||
name='AddExistingChannelView'
|
||||
|
@ -252,13 +252,9 @@ const NewMessageStackNavigator = () => {
|
|||
<NewMessageStack.Navigator
|
||||
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}
|
||||
>
|
||||
<NewMessageStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} />
|
||||
<NewMessageStack.Screen name='NewMessageView' component={NewMessageView} />
|
||||
<NewMessageStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} />
|
||||
<NewMessageStack.Screen
|
||||
name='CreateChannelView'
|
||||
component={CreateChannelView}
|
||||
options={CreateChannelView.navigationOptions}
|
||||
/>
|
||||
<NewMessageStack.Screen name='CreateChannelView' component={CreateChannelView} />
|
||||
<NewMessageStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} />
|
||||
</NewMessageStack.Navigator>
|
||||
);
|
||||
|
|
|
@ -187,9 +187,9 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
|||
<ModalStack.Screen name='ProfileView' component={ProfileView} />
|
||||
<ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} />
|
||||
<ModalStack.Screen name='AdminPanelView' component={AdminPanelView} />
|
||||
<ModalStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} />
|
||||
<ModalStack.Screen name='NewMessageView' component={NewMessageView} />
|
||||
<ModalStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} />
|
||||
<ModalStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} />
|
||||
<ModalStack.Screen name='CreateChannelView' component={CreateChannelView} />
|
||||
<ModalStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} />
|
||||
<ModalStack.Screen name='E2ESaveYourPasswordView' component={E2ESaveYourPasswordView} />
|
||||
<ModalStack.Screen name='E2EHowItWorksView' component={E2EHowItWorksView} />
|
||||
|
|
|
@ -1,427 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { FlatList, ScrollView, StyleSheet, Switch, Text, View, SwitchProps } from 'react-native';
|
||||
import { dequal } from 'dequal';
|
||||
|
||||
import * as List from '../containers/List';
|
||||
import { TextInput } from '../containers/TextInput';
|
||||
import { sendLoadingEvent } from '../containers/Loading';
|
||||
import { createChannelRequest } from '../actions/createChannel';
|
||||
import { removeUser } from '../actions/selectedUsers';
|
||||
import KeyboardView from '../containers/KeyboardView';
|
||||
import scrollPersistTaps from '../lib/methods/helpers/scrollPersistTaps';
|
||||
import I18n from '../i18n';
|
||||
import UserItem from '../containers/UserItem';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { SWITCH_TRACK_COLOR, themes } from '../lib/constants';
|
||||
import { withTheme } from '../theme';
|
||||
import { Review } from '../lib/methods/helpers/review';
|
||||
import { getUserSelector } from '../selectors/login';
|
||||
import { events, logEvent } from '../lib/methods/helpers/log';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import sharedStyles from './Styles';
|
||||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import { IApplicationState, IBaseScreen, IUser } from '../definitions';
|
||||
import { hasPermission } from '../lib/methods/helpers';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
list: {
|
||||
width: '100%'
|
||||
},
|
||||
input: {
|
||||
height: 54,
|
||||
paddingHorizontal: 18,
|
||||
fontSize: 17,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
switchContainer: {
|
||||
height: 54,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 18
|
||||
},
|
||||
label: {
|
||||
fontSize: 17,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
invitedHeader: {
|
||||
marginTop: 18,
|
||||
marginHorizontal: 15,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
},
|
||||
invitedTitle: {
|
||||
fontSize: 18,
|
||||
...sharedStyles.textSemibold,
|
||||
lineHeight: 41
|
||||
},
|
||||
invitedCount: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
||||
|
||||
interface IOtherUser {
|
||||
_id: string;
|
||||
name: string;
|
||||
fname: string;
|
||||
}
|
||||
|
||||
interface ICreateChannelViewState {
|
||||
channelName: string;
|
||||
type: boolean;
|
||||
readOnly: boolean;
|
||||
encrypted: boolean;
|
||||
broadcast: boolean;
|
||||
isTeam: boolean;
|
||||
permissions: boolean[];
|
||||
}
|
||||
|
||||
interface ICreateChannelViewProps extends IBaseScreen<ChatsStackParamList, 'CreateChannelView'> {
|
||||
baseUrl: string;
|
||||
error: object;
|
||||
failure: boolean;
|
||||
isFetching: boolean;
|
||||
encryptionEnabled: boolean;
|
||||
users: IOtherUser[];
|
||||
user: IUser;
|
||||
teamId: string;
|
||||
createPublicChannelPermission: string[] | undefined;
|
||||
createPrivateChannelPermission: string[] | undefined;
|
||||
}
|
||||
|
||||
interface ISwitch extends SwitchProps {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreateChannelViewState> {
|
||||
private teamId?: string;
|
||||
|
||||
constructor(props: ICreateChannelViewProps) {
|
||||
super(props);
|
||||
const { route } = this.props;
|
||||
const isTeam = route?.params?.isTeam || false;
|
||||
this.teamId = route?.params?.teamId;
|
||||
this.state = {
|
||||
channelName: '',
|
||||
type: true,
|
||||
readOnly: false,
|
||||
encrypted: false,
|
||||
broadcast: false,
|
||||
isTeam,
|
||||
permissions: []
|
||||
};
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.handleHasPermission();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: ICreateChannelViewProps, nextState: ICreateChannelViewState) {
|
||||
const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state;
|
||||
const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } =
|
||||
this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.channelName !== channelName) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.type !== type) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.readOnly !== readOnly) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.encrypted !== encrypted) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.broadcast !== broadcast) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.permissions !== permissions) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.isFetching !== isFetching) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.encryptionEnabled !== encryptionEnabled) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextProps.createPublicChannelPermission, createPublicChannelPermission)) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextProps.createPrivateChannelPermission, createPrivateChannelPermission)) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextProps.users, users)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ICreateChannelViewProps) {
|
||||
const { createPublicChannelPermission, createPrivateChannelPermission, isFetching } = this.props;
|
||||
if (
|
||||
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
|
||||
!dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission)
|
||||
) {
|
||||
this.handleHasPermission();
|
||||
}
|
||||
if (isFetching !== prevProps.isFetching) {
|
||||
sendLoadingEvent({ visible: isFetching });
|
||||
}
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { navigation } = this.props;
|
||||
const { isTeam } = this.state;
|
||||
|
||||
navigation.setOptions({
|
||||
title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')
|
||||
});
|
||||
};
|
||||
|
||||
toggleRightButton = (channelName: string) => {
|
||||
const { navigation } = this.props;
|
||||
navigation.setOptions({
|
||||
headerRight: () =>
|
||||
channelName.trim().length > 0 && (
|
||||
<HeaderButton.Container>
|
||||
<HeaderButton.Item title={I18n.t('Create')} onPress={this.submit} testID='create-channel-submit' />
|
||||
</HeaderButton.Container>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
onChangeText = (channelName: string) => {
|
||||
this.toggleRightButton(channelName);
|
||||
this.setState({ channelName });
|
||||
};
|
||||
|
||||
submit = () => {
|
||||
const { channelName, type, readOnly, broadcast, encrypted, isTeam } = this.state;
|
||||
const { users: usersProps, isFetching, dispatch } = this.props;
|
||||
|
||||
if (!channelName.trim() || isFetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
// transform users object into array of usernames
|
||||
const users = usersProps.map(user => user.name);
|
||||
|
||||
// create channel or team
|
||||
const data = {
|
||||
name: channelName,
|
||||
users,
|
||||
type,
|
||||
readOnly,
|
||||
broadcast,
|
||||
encrypted,
|
||||
isTeam,
|
||||
teamId: this.teamId
|
||||
};
|
||||
dispatch(createChannelRequest(data));
|
||||
Review.pushPositiveEvent();
|
||||
};
|
||||
|
||||
removeUser = (user: IOtherUser) => {
|
||||
logEvent(events.CR_REMOVE_USER);
|
||||
const { dispatch } = this.props;
|
||||
dispatch(removeUser(user));
|
||||
};
|
||||
|
||||
renderSwitch = ({ id, value, label, onValueChange, disabled = false }: ISwitch) => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<View style={[styles.switchContainer, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t(label)}</Text>
|
||||
<Switch
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
testID={`create-channel-${id}`}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
handleHasPermission = async () => {
|
||||
const { createPublicChannelPermission, createPrivateChannelPermission } = this.props;
|
||||
const permissions = [createPublicChannelPermission, createPrivateChannelPermission];
|
||||
const permissionsToCreate = await hasPermission(permissions);
|
||||
this.setState({ permissions: permissionsToCreate });
|
||||
};
|
||||
|
||||
renderType() {
|
||||
const { type, isTeam, permissions } = this.state;
|
||||
const isDisabled = permissions.filter(r => r === true).length <= 1;
|
||||
|
||||
return this.renderSwitch({
|
||||
id: 'type',
|
||||
value: permissions[1] ? type : false,
|
||||
disabled: isDisabled,
|
||||
label: isTeam ? 'Private_Team' : 'Private_Channel',
|
||||
onValueChange: (value: boolean) => {
|
||||
logEvent(events.CR_TOGGLE_TYPE);
|
||||
// If we set the channel as public, encrypted status should be false
|
||||
this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderReadOnly() {
|
||||
const { readOnly, broadcast, isTeam } = this.state;
|
||||
|
||||
return this.renderSwitch({
|
||||
id: 'readonly',
|
||||
value: readOnly,
|
||||
label: isTeam ? 'Read_Only_Team' : 'Read_Only_Channel',
|
||||
onValueChange: value => {
|
||||
logEvent(events.CR_TOGGLE_READ_ONLY);
|
||||
this.setState({ readOnly: value });
|
||||
},
|
||||
disabled: broadcast
|
||||
});
|
||||
}
|
||||
|
||||
renderEncrypted() {
|
||||
const { type, encrypted } = this.state;
|
||||
const { encryptionEnabled } = this.props;
|
||||
|
||||
if (!encryptionEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.renderSwitch({
|
||||
id: 'encrypted',
|
||||
value: encrypted,
|
||||
label: 'Encrypted',
|
||||
onValueChange: value => {
|
||||
logEvent(events.CR_TOGGLE_ENCRYPTED);
|
||||
this.setState({ encrypted: value });
|
||||
},
|
||||
disabled: !type
|
||||
});
|
||||
}
|
||||
|
||||
renderBroadcast() {
|
||||
const { broadcast, readOnly, isTeam } = this.state;
|
||||
|
||||
return this.renderSwitch({
|
||||
id: 'broadcast',
|
||||
value: broadcast,
|
||||
label: isTeam ? 'Broadcast_Team' : 'Broadcast_Channel',
|
||||
onValueChange: value => {
|
||||
logEvent(events.CR_TOGGLE_BROADCAST);
|
||||
this.setState({
|
||||
broadcast: value,
|
||||
readOnly: value ? true : readOnly
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderItem = ({ item }: { item: IOtherUser }) => (
|
||||
<UserItem
|
||||
name={item.fname}
|
||||
username={item.name}
|
||||
onPress={() => this.removeUser(item)}
|
||||
testID={`create-channel-view-item-${item.name}`}
|
||||
icon='check'
|
||||
/>
|
||||
);
|
||||
|
||||
renderInvitedList = () => {
|
||||
const { users, theme } = this.props;
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={users}
|
||||
extraData={users}
|
||||
keyExtractor={item => item._id}
|
||||
style={[
|
||||
styles.list,
|
||||
sharedStyles.separatorVertical,
|
||||
{
|
||||
backgroundColor: themes[theme].focusedBackground,
|
||||
borderColor: themes[theme].separatorColor
|
||||
}
|
||||
]}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { channelName, isTeam } = this.state;
|
||||
const { users, theme } = this.props;
|
||||
const userCount = users.length;
|
||||
|
||||
return (
|
||||
<KeyboardView
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
contentContainerStyle={[sharedStyles.container, styles.container]}
|
||||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<StatusBar />
|
||||
<SafeAreaView testID='create-channel-view'>
|
||||
<ScrollView {...scrollPersistTaps}>
|
||||
<View style={[sharedStyles.separatorVertical, { borderColor: themes[theme].separatorColor }]}>
|
||||
<TextInput
|
||||
autoFocus
|
||||
style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]}
|
||||
value={channelName}
|
||||
onChangeText={this.onChangeText}
|
||||
placeholder={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
|
||||
returnKeyType='done'
|
||||
testID='create-channel-name'
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
<List.Separator />
|
||||
{this.renderType()}
|
||||
<List.Separator />
|
||||
{this.renderReadOnly()}
|
||||
<List.Separator />
|
||||
{this.renderEncrypted()}
|
||||
<List.Separator />
|
||||
{this.renderBroadcast()}
|
||||
</View>
|
||||
<View style={styles.invitedHeader}>
|
||||
<Text style={[styles.invitedTitle, { color: themes[theme].titleText }]}>{I18n.t('Invite')}</Text>
|
||||
<Text style={[styles.invitedCount, { color: themes[theme].auxiliaryText }]}>
|
||||
{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}
|
||||
</Text>
|
||||
</View>
|
||||
{this.renderInvitedList()}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
baseUrl: state.server.server,
|
||||
isFetching: state.createChannel.isFetching,
|
||||
encryptionEnabled: state.encryption.enabled,
|
||||
users: state.selectedUsers.users,
|
||||
user: getUserSelector(state),
|
||||
createPublicChannelPermission: state.permissions['create-c'],
|
||||
createPrivateChannelPermission: state.permissions['create-p']
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(CreateChannelView));
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
|
||||
import { SwitchItem } from './SwitchItem';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'flex-start',
|
||||
padding: 16
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
title: 'SwitchItem'
|
||||
};
|
||||
|
||||
const testSwitch = {
|
||||
id: 'switch-id',
|
||||
hint: 'Read_only_hint',
|
||||
label: 'Onboarding_title',
|
||||
onValueChange: () => {},
|
||||
value: false,
|
||||
testSwitchID: 'create-channel-switch-id',
|
||||
testLabelID: 'create-channel-switch-id-hint'
|
||||
};
|
||||
|
||||
export const Switch = () => (
|
||||
<>
|
||||
<View style={styles.container}>
|
||||
<SwitchItem
|
||||
hint={testSwitch.hint}
|
||||
id={testSwitch.id}
|
||||
label={testSwitch.label}
|
||||
onValueChange={() => testSwitch.onValueChange()}
|
||||
value={testSwitch.value}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
|
@ -0,0 +1,68 @@
|
|||
import React from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import i18n from '../../../i18n';
|
||||
import { SwitchItem, ISwitch } from './SwitchItem';
|
||||
import { mockedStore as store } from '../../../reducers/mockedStore';
|
||||
|
||||
const onPressMock = jest.fn((value: boolean) => value);
|
||||
|
||||
const testSwitch = {
|
||||
id: 'switch-id',
|
||||
hint: 'Read_only_hint',
|
||||
label: 'Onboarding_title',
|
||||
onValueChange: onPressMock,
|
||||
value: false,
|
||||
testSwitchID: 'create-channel-switch-id',
|
||||
testLabelID: 'create-channel-switch-id-hint'
|
||||
};
|
||||
|
||||
const Render = ({ hint, id, label, onValueChange, value }: ISwitch) => (
|
||||
<Provider store={store}>
|
||||
<SwitchItem hint={hint} id={id} label={label} onValueChange={onValueChange} value={value} />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
describe('SwitchItemEncrypted', () => {
|
||||
it('should not render the Encrypted Switch component', async () => {
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
hint={testSwitch.hint}
|
||||
id={testSwitch.id}
|
||||
label={testSwitch.label}
|
||||
onValueChange={value => testSwitch.onValueChange(value)}
|
||||
value={testSwitch.value}
|
||||
/>
|
||||
);
|
||||
const component = await findByTestId(testSwitch.testSwitchID);
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should change value of switch', async () => {
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
hint={testSwitch.hint}
|
||||
id={testSwitch.id}
|
||||
label={testSwitch.label}
|
||||
onValueChange={value => testSwitch.onValueChange(value)}
|
||||
value={testSwitch.value}
|
||||
/>
|
||||
);
|
||||
const component = await findByTestId(testSwitch.testSwitchID);
|
||||
fireEvent(component, 'valueChange', { value: true });
|
||||
expect(onPressMock).toHaveReturnedWith({ value: !testSwitch.value });
|
||||
});
|
||||
it('check if hint exists and is the same from testSwitch object', async () => {
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
hint={testSwitch.hint}
|
||||
id={testSwitch.id}
|
||||
label={testSwitch.label}
|
||||
onValueChange={value => testSwitch.onValueChange(value)}
|
||||
value={testSwitch.value}
|
||||
/>
|
||||
);
|
||||
const component = await findByTestId(testSwitch.testLabelID);
|
||||
expect(component.props.children).toBe(i18n.t(testSwitch.hint));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Switch, Text, View, SwitchProps } from 'react-native';
|
||||
|
||||
import I18n from '../../../i18n';
|
||||
import { SWITCH_TRACK_COLOR } from '../../../lib/constants';
|
||||
import { useTheme } from '../../../theme';
|
||||
import sharedStyles from '../../Styles';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
switchContainer: {
|
||||
minHeight: 54,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
maxHeight: 80,
|
||||
marginBottom: 12
|
||||
},
|
||||
switchTextContainer: {
|
||||
flex: 1,
|
||||
marginRight: 8
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
hint: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
||||
|
||||
export interface ISwitch extends SwitchProps {
|
||||
id: string;
|
||||
label: string;
|
||||
hint: string;
|
||||
onValueChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const SwitchItem = ({ id, value, label, hint, onValueChange, disabled = false }: ISwitch) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<View style={[styles.switchContainer, { backgroundColor: colors.backgroundColor }]}>
|
||||
<View style={styles.switchTextContainer}>
|
||||
<Text style={[styles.label, { color: colors.titleText }]}>{I18n.t(label)}</Text>
|
||||
<Text testID={`create-channel-${id}-hint`} style={[styles.hint, { color: colors.auxiliaryText }]}>
|
||||
{I18n.t(hint)}
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
testID={`create-channel-${id}`}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,108 @@
|
|||
import React from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { SwitchItemEncrypted, ISwitchItemEncrypted } from './SwitchItemEncrypted';
|
||||
import { mockedStore as store } from '../../../reducers/mockedStore';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const onPressMock = jest.fn((value: boolean) => value);
|
||||
|
||||
const testEncrypted = {
|
||||
encrypted: false,
|
||||
encryptionEnabled: false,
|
||||
isTeam: false,
|
||||
onValueChangeEncrypted: onPressMock,
|
||||
type: false,
|
||||
testSwitchID: 'create-channel-encrypted',
|
||||
testLabelID: `create-channel-encrypted-hint`
|
||||
};
|
||||
|
||||
const Render = ({ encrypted, encryptionEnabled, isTeam, onValueChangeEncrypted, type }: ISwitchItemEncrypted) => (
|
||||
<Provider store={store}>
|
||||
<SwitchItemEncrypted
|
||||
encrypted={encrypted}
|
||||
encryptionEnabled={encryptionEnabled}
|
||||
isTeam={isTeam}
|
||||
onValueChangeEncrypted={onValueChangeEncrypted}
|
||||
type={type}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
describe('SwitchItemEncrypted', () => {
|
||||
it('should not render the Encrypted Switch component', async () => {
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
encrypted={testEncrypted.encrypted}
|
||||
encryptionEnabled={testEncrypted.encryptionEnabled}
|
||||
isTeam={testEncrypted.isTeam}
|
||||
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
|
||||
type={testEncrypted.type}
|
||||
/>
|
||||
);
|
||||
try {
|
||||
await findByTestId(testEncrypted.testSwitchID);
|
||||
} catch (e) {
|
||||
expect(e).toBeTruthy();
|
||||
}
|
||||
});
|
||||
it('should render the Encrypted Switch component', async () => {
|
||||
testEncrypted.encryptionEnabled = true;
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
encrypted={testEncrypted.encrypted}
|
||||
encryptionEnabled={testEncrypted.encryptionEnabled}
|
||||
isTeam={testEncrypted.isTeam}
|
||||
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
|
||||
type={testEncrypted.type}
|
||||
/>
|
||||
);
|
||||
const component = await findByTestId(testEncrypted.testSwitchID);
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should change value of switch', async () => {
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
encrypted={testEncrypted.encrypted}
|
||||
encryptionEnabled={testEncrypted.encryptionEnabled}
|
||||
isTeam={testEncrypted.isTeam}
|
||||
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
|
||||
type={testEncrypted.type}
|
||||
/>
|
||||
);
|
||||
|
||||
const component = await findByTestId(testEncrypted.testSwitchID);
|
||||
fireEvent(component, 'valueChange', { value: true });
|
||||
expect(onPressMock).toHaveReturnedWith({ value: !testEncrypted.encrypted });
|
||||
});
|
||||
it('label when encrypted and isTeam are false and is a public channel', async () => {
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
encrypted={testEncrypted.encrypted}
|
||||
encryptionEnabled={testEncrypted.encryptionEnabled}
|
||||
isTeam={testEncrypted.isTeam}
|
||||
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
|
||||
type={testEncrypted.type}
|
||||
/>
|
||||
);
|
||||
const component = await findByTestId(testEncrypted.testLabelID);
|
||||
expect(component.props.children).toBe(i18n.t('Channel_hint_encrypted_not_available'));
|
||||
});
|
||||
it('label when encrypted and isTeam are true and is a private team', async () => {
|
||||
testEncrypted.isTeam = true;
|
||||
testEncrypted.type = true;
|
||||
testEncrypted.encrypted = true;
|
||||
const { findByTestId } = render(
|
||||
<Render
|
||||
encrypted={testEncrypted.encrypted}
|
||||
encryptionEnabled={testEncrypted.encryptionEnabled}
|
||||
isTeam={testEncrypted.isTeam}
|
||||
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
|
||||
type={testEncrypted.type}
|
||||
/>
|
||||
);
|
||||
const component = await findByTestId(testEncrypted.testLabelID);
|
||||
expect(component.props.children).toBe(i18n.t('Team_hint_encrypted'));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
import React from 'react';
|
||||
|
||||
import { SwitchItem } from './SwitchItem';
|
||||
|
||||
export interface ISwitchItemEncrypted {
|
||||
encryptionEnabled: boolean;
|
||||
isTeam: boolean;
|
||||
type: boolean;
|
||||
encrypted: boolean;
|
||||
onValueChangeEncrypted: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const SwitchItemEncrypted = ({
|
||||
encryptionEnabled,
|
||||
isTeam,
|
||||
type,
|
||||
encrypted,
|
||||
onValueChangeEncrypted
|
||||
}: ISwitchItemEncrypted) => {
|
||||
if (!encryptionEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let hint = '';
|
||||
if (isTeam && type) {
|
||||
hint = 'Team_hint_encrypted';
|
||||
}
|
||||
if (isTeam && !type) {
|
||||
hint = 'Team_hint_encrypted_not_available';
|
||||
}
|
||||
if (!isTeam && type) {
|
||||
hint = 'Channel_hint_encrypted';
|
||||
}
|
||||
if (!isTeam && !type) {
|
||||
hint = 'Channel_hint_encrypted_not_available';
|
||||
}
|
||||
|
||||
return (
|
||||
<SwitchItem
|
||||
id={'encrypted'}
|
||||
value={encrypted}
|
||||
label={'Encrypted'}
|
||||
hint={hint}
|
||||
onValueChange={onValueChangeEncrypted}
|
||||
disabled={!type}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
|
||||
import { SwitchItem } from './SwitchItem';
|
||||
|
||||
export const SwitchItemReadOnly = ({
|
||||
readOnly,
|
||||
isTeam,
|
||||
onValueChangeReadOnly,
|
||||
broadcast
|
||||
}: {
|
||||
readOnly: boolean;
|
||||
isTeam: boolean;
|
||||
onValueChangeReadOnly: (value: boolean) => void;
|
||||
broadcast: boolean;
|
||||
}) => {
|
||||
let hint = '';
|
||||
if (readOnly) {
|
||||
hint = 'Read_only_hint';
|
||||
}
|
||||
if (isTeam && !readOnly) {
|
||||
hint = 'Team_hint_not_read_only';
|
||||
}
|
||||
if (!isTeam && !readOnly) {
|
||||
hint = 'Channel_hint_not_read_only';
|
||||
}
|
||||
|
||||
return (
|
||||
<SwitchItem
|
||||
id={'readonly'}
|
||||
value={readOnly}
|
||||
label={'Read_Only'}
|
||||
hint={hint}
|
||||
onValueChange={onValueChangeReadOnly}
|
||||
disabled={broadcast}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
|
||||
import { usePermissions } from '../../../lib/hooks';
|
||||
import { SwitchItem } from './SwitchItem';
|
||||
|
||||
export const SwitchItemType = ({
|
||||
isTeam,
|
||||
type,
|
||||
onValueChangeType
|
||||
}: {
|
||||
isTeam: boolean;
|
||||
type: boolean;
|
||||
onValueChangeType: (value: boolean) => void;
|
||||
}) => {
|
||||
const [createChannelPermission, createPrivateChannelPermission] = usePermissions(['create-c', 'create-p']);
|
||||
|
||||
const isDisabled = [createChannelPermission, createPrivateChannelPermission].filter(r => r === true).length <= 1;
|
||||
|
||||
let hint = '';
|
||||
if (isTeam && type) {
|
||||
hint = 'Team_hint_private';
|
||||
}
|
||||
if (isTeam && !type) {
|
||||
hint = 'Team_hint_public';
|
||||
}
|
||||
if (!isTeam && type) {
|
||||
hint = 'Channel_hint_private';
|
||||
}
|
||||
if (!isTeam && !type) {
|
||||
hint = 'Channel_hint_public';
|
||||
}
|
||||
|
||||
return (
|
||||
<SwitchItem
|
||||
id={'type'}
|
||||
value={createPrivateChannelPermission ? type : false}
|
||||
disabled={isDisabled}
|
||||
label={'Private'}
|
||||
hint={hint}
|
||||
onValueChange={onValueChangeType}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { UseFormSetValue } from 'react-hook-form';
|
||||
|
||||
import { useAppSelector } from '../../../lib/hooks';
|
||||
import { events, logEvent } from '../../../lib/methods/helpers/log';
|
||||
import { SwitchItem } from './SwitchItem';
|
||||
import { SwitchItemType } from './SwitchItemType';
|
||||
import { SwitchItemReadOnly } from './SwitchItemReadOnly';
|
||||
import { SwitchItemEncrypted } from './SwitchItemEncrypted';
|
||||
import { IFormData } from '..';
|
||||
|
||||
export const RoomSettings = ({ isTeam, setValue }: { isTeam: boolean; setValue: UseFormSetValue<IFormData> }) => {
|
||||
const [type, setType] = useState(true);
|
||||
const [readOnly, setReadOnly] = useState(false);
|
||||
const [encrypted, setEncrypted] = useState(false);
|
||||
const [broadcast, setBroadcast] = useState(false);
|
||||
|
||||
const { encryptionEnabled } = useAppSelector(state => ({
|
||||
encryptionEnabled: state.encryption.enabled
|
||||
}));
|
||||
|
||||
const onValueChangeType = useCallback(
|
||||
(value: boolean) => {
|
||||
logEvent(events.CR_TOGGLE_TYPE);
|
||||
// If we set the channel as public, encrypted status should be false
|
||||
setType(value);
|
||||
setValue('type', value);
|
||||
setEncrypted(value && encrypted);
|
||||
setValue('encrypted', value && encrypted);
|
||||
},
|
||||
[encrypted]
|
||||
);
|
||||
|
||||
const onValueChangeReadOnly = useCallback((value: boolean) => {
|
||||
logEvent(events.CR_TOGGLE_READ_ONLY);
|
||||
setReadOnly(value);
|
||||
setValue('readOnly', value);
|
||||
}, []);
|
||||
|
||||
const onValueChangeEncrypted = useCallback((value: boolean) => {
|
||||
logEvent(events.CR_TOGGLE_ENCRYPTED);
|
||||
setEncrypted(value);
|
||||
setValue('encrypted', value);
|
||||
}, []);
|
||||
|
||||
const onValueChangeBroadcast = (value: boolean) => {
|
||||
logEvent(events.CR_TOGGLE_BROADCAST);
|
||||
setBroadcast(value);
|
||||
setValue('broadcast', value);
|
||||
setReadOnly(value ? true : readOnly);
|
||||
setValue('readOnly', value ? true : readOnly);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<SwitchItemType isTeam={isTeam} type={type} onValueChangeType={onValueChangeType} />
|
||||
<SwitchItemReadOnly
|
||||
broadcast={broadcast}
|
||||
isTeam={isTeam}
|
||||
readOnly={readOnly}
|
||||
onValueChangeReadOnly={onValueChangeReadOnly}
|
||||
/>
|
||||
<SwitchItemEncrypted
|
||||
encryptionEnabled={encryptionEnabled}
|
||||
isTeam={isTeam}
|
||||
type={type}
|
||||
encrypted={encrypted}
|
||||
onValueChangeEncrypted={onValueChangeEncrypted}
|
||||
/>
|
||||
<SwitchItem
|
||||
id={'broadcast'}
|
||||
value={broadcast}
|
||||
label={'Broadcast'}
|
||||
hint={'Broadcast_hint'}
|
||||
onValueChange={onValueChangeBroadcast}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,205 @@
|
|||
import React, { useCallback, useEffect, useLayoutEffect } from 'react';
|
||||
import { shallowEqual, useDispatch } from 'react-redux';
|
||||
import { FlatList, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { sendLoadingEvent } from '../../containers/Loading';
|
||||
import { createChannelRequest } from '../../actions/createChannel';
|
||||
import { removeUser as removeUserAction } from '../../actions/selectedUsers';
|
||||
import KeyboardView from '../../containers/KeyboardView';
|
||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||
import I18n from '../../i18n';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { useTheme } from '../../theme';
|
||||
import { Review } from '../../lib/methods/helpers/review';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import sharedStyles from '../Styles';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import Button from '../../containers/Button';
|
||||
import { ControlledFormTextInput } from '../../containers/TextInput';
|
||||
import Chip from '../../containers/Chip';
|
||||
import { RoomSettings } from './RoomSettings';
|
||||
import { ISelectedUser } from '../../reducers/selectedUsers';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
containerTextInput: {
|
||||
paddingHorizontal: 16,
|
||||
marginTop: 16
|
||||
},
|
||||
containerStyle: {
|
||||
marginBottom: 28
|
||||
},
|
||||
list: {
|
||||
width: '100%'
|
||||
},
|
||||
invitedHeader: {
|
||||
marginVertical: 12,
|
||||
marginHorizontal: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
},
|
||||
invitedCount: {
|
||||
fontSize: 12,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
invitedList: {
|
||||
paddingHorizontal: 16
|
||||
},
|
||||
buttonCreate: {
|
||||
marginHorizontal: 16,
|
||||
marginTop: 24
|
||||
}
|
||||
});
|
||||
|
||||
export interface IFormData {
|
||||
channelName: string;
|
||||
type: boolean;
|
||||
readOnly: boolean;
|
||||
encrypted: boolean;
|
||||
broadcast: boolean;
|
||||
}
|
||||
|
||||
const CreateChannelView = () => {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isDirty },
|
||||
setValue
|
||||
} = useForm<IFormData>({
|
||||
defaultValues: { channelName: '', broadcast: false, encrypted: false, readOnly: false, type: false }
|
||||
});
|
||||
|
||||
const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'CreateChannelView'>>();
|
||||
const { params } = useRoute<RouteProp<ChatsStackParamList, 'CreateChannelView'>>();
|
||||
const isTeam = params?.isTeam || false;
|
||||
const teamId = params?.teamId;
|
||||
const { colors } = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { isFetching, useRealName, users } = useAppSelector(
|
||||
state => ({
|
||||
isFetching: state.createChannel.isFetching,
|
||||
users: state.selectedUsers.users,
|
||||
useRealName: state.settings.UI_Use_Real_Name as boolean
|
||||
}),
|
||||
shallowEqual
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
sendLoadingEvent({ visible: isFetching });
|
||||
}, [isFetching]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')
|
||||
});
|
||||
}, [isTeam, navigation]);
|
||||
|
||||
const removeUser = useCallback(
|
||||
(user: ISelectedUser) => {
|
||||
dispatch(removeUserAction(user));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const submit = ({ channelName, broadcast, encrypted, readOnly, type }: IFormData) => {
|
||||
if (!channelName.trim() || isFetching) {
|
||||
return;
|
||||
}
|
||||
// transform users object into array of usernames
|
||||
const usersMapped = users.map(user => user.name);
|
||||
// create channel or team
|
||||
const data = {
|
||||
name: channelName,
|
||||
users: usersMapped,
|
||||
type,
|
||||
readOnly,
|
||||
broadcast,
|
||||
encrypted,
|
||||
isTeam,
|
||||
teamId
|
||||
};
|
||||
dispatch(createChannelRequest(data));
|
||||
Review.pushPositiveEvent();
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardView
|
||||
style={{ backgroundColor: colors.backgroundColor }}
|
||||
contentContainerStyle={[sharedStyles.container, styles.container]}
|
||||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<StatusBar />
|
||||
<SafeAreaView style={{ backgroundColor: colors.backgroundColor }} testID='create-channel-view'>
|
||||
<ScrollView {...scrollPersistTaps}>
|
||||
<View style={[styles.containerTextInput, { borderColor: colors.separatorColor }]}>
|
||||
<ControlledFormTextInput
|
||||
label={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
|
||||
testID='create-channel-name'
|
||||
returnKeyType='done'
|
||||
containerStyle={styles.containerStyle}
|
||||
name={'channelName'}
|
||||
control={control}
|
||||
/>
|
||||
<RoomSettings isTeam={isTeam} setValue={setValue} />
|
||||
</View>
|
||||
{users.length > 0 ? (
|
||||
<>
|
||||
<View style={styles.invitedHeader}>
|
||||
<Text style={[styles.invitedCount, { color: colors.auxiliaryText }]}>
|
||||
{I18n.t('N_Selected_members', { n: users.length })}
|
||||
</Text>
|
||||
</View>
|
||||
<FlatList
|
||||
data={users}
|
||||
extraData={users}
|
||||
keyExtractor={item => item._id}
|
||||
style={[
|
||||
styles.list,
|
||||
{
|
||||
backgroundColor: colors.backgroundColor,
|
||||
borderColor: colors.separatorColor
|
||||
}
|
||||
]}
|
||||
contentContainerStyle={styles.invitedList}
|
||||
renderItem={({ item }) => {
|
||||
const name = useRealName && item.fname ? item.fname : item.name;
|
||||
const username = item.name;
|
||||
|
||||
return (
|
||||
<Chip
|
||||
text={name}
|
||||
avatar={username}
|
||||
onPress={() => removeUser(item)}
|
||||
testID={`create-channel-view-item-${item.name}`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
keyboardShouldPersistTaps='always'
|
||||
horizontal
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
<Button
|
||||
title={isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')}
|
||||
type='primary'
|
||||
onPress={handleSubmit(submit)}
|
||||
disabled={!isDirty}
|
||||
testID='create-channel-submit'
|
||||
loading={isFetching}
|
||||
style={styles.buttonCreate}
|
||||
/>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardView>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateChannelView;
|
|
@ -1,335 +0,0 @@
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { dequal } from 'dequal';
|
||||
import React from 'react';
|
||||
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { createChannelRequest } from '../actions/createChannel';
|
||||
import { themes } from '../lib/constants';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import * as List from '../containers/List';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import SearchBox from '../containers/SearchBox';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { IApplicationState, IBaseScreen, ISearch, TSubscriptionModel } from '../definitions';
|
||||
import I18n from '../i18n';
|
||||
import database from '../lib/database';
|
||||
import { CustomIcon, TIconsName } from '../containers/CustomIcon';
|
||||
import Navigation from '../lib/navigation/appNavigation';
|
||||
import UserItem from '../containers/UserItem';
|
||||
import { withTheme } from '../theme';
|
||||
import { goRoom, TGoRoomItem } from '../lib/methods/helpers/goRoom';
|
||||
import log, { events, logEvent } from '../lib/methods/helpers/log';
|
||||
import Touch from '../containers/Touch';
|
||||
import sharedStyles from './Styles';
|
||||
import { NewMessageStackParamList } from '../stacks/types';
|
||||
import { search } from '../lib/methods';
|
||||
import { hasPermission, compareServerVersion } from '../lib/methods/helpers';
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
height: 46,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
buttonIcon: {
|
||||
marginLeft: 18,
|
||||
marginRight: 16
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 17,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
buttonContainer: {
|
||||
paddingBottom: 16
|
||||
}
|
||||
});
|
||||
|
||||
interface IButton {
|
||||
onPress: () => void;
|
||||
testID: string;
|
||||
title: string;
|
||||
icon: TIconsName;
|
||||
first?: boolean;
|
||||
}
|
||||
|
||||
interface INewMessageViewState {
|
||||
search: (ISearch | TSubscriptionModel)[];
|
||||
chats: TSubscriptionModel[];
|
||||
permissions: boolean[];
|
||||
}
|
||||
|
||||
interface INewMessageViewProps extends IBaseScreen<NewMessageStackParamList, 'NewMessageView'> {
|
||||
maxUsers: number;
|
||||
isMasterDetail: boolean;
|
||||
serverVersion: string;
|
||||
createTeamPermission?: string[];
|
||||
createDirectMessagePermission?: string[];
|
||||
createPublicChannelPermission?: string[];
|
||||
createPrivateChannelPermission?: string[];
|
||||
createDiscussionPermission?: string[];
|
||||
}
|
||||
|
||||
class NewMessageView extends React.Component<INewMessageViewProps, INewMessageViewState> {
|
||||
static navigationOptions = ({ navigation }: INewMessageViewProps): StackNavigationOptions => ({
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />,
|
||||
title: I18n.t('New_Message')
|
||||
});
|
||||
|
||||
constructor(props: INewMessageViewProps) {
|
||||
super(props);
|
||||
this.init();
|
||||
this.state = {
|
||||
search: [],
|
||||
chats: [],
|
||||
permissions: []
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
init = async () => {
|
||||
try {
|
||||
const db = database.active;
|
||||
const chats = await db
|
||||
.get('subscriptions')
|
||||
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
|
||||
.fetch();
|
||||
|
||||
this.setState({ chats });
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.handleHasPermission();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: INewMessageViewProps) {
|
||||
const {
|
||||
createTeamPermission,
|
||||
createPublicChannelPermission,
|
||||
createPrivateChannelPermission,
|
||||
createDirectMessagePermission,
|
||||
createDiscussionPermission
|
||||
} = this.props;
|
||||
|
||||
if (
|
||||
!dequal(createTeamPermission, prevProps.createTeamPermission) ||
|
||||
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
|
||||
!dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) ||
|
||||
!dequal(createDirectMessagePermission, prevProps.createDirectMessagePermission) ||
|
||||
!dequal(createDiscussionPermission, prevProps.createDiscussionPermission)
|
||||
) {
|
||||
this.handleHasPermission();
|
||||
}
|
||||
}
|
||||
|
||||
handleSearch = async (text: string) => {
|
||||
const result = (await search({ text, filterRooms: false })) as ISearch[];
|
||||
this.setState({
|
||||
search: result
|
||||
});
|
||||
};
|
||||
|
||||
onSearchChangeText(text: string) {
|
||||
this.handleSearch(text);
|
||||
}
|
||||
|
||||
dismiss = () => {
|
||||
const { navigation } = this.props;
|
||||
return navigation.pop();
|
||||
};
|
||||
|
||||
createChannel = () => {
|
||||
logEvent(events.NEW_MSG_CREATE_CHANNEL);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
|
||||
};
|
||||
|
||||
createTeam = () => {
|
||||
logEvent(events.NEW_MSG_CREATE_TEAM);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('SelectedUsersViewCreateChannel', {
|
||||
nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true })
|
||||
});
|
||||
};
|
||||
|
||||
createGroupChat = () => {
|
||||
logEvent(events.NEW_MSG_CREATE_GROUP_CHAT);
|
||||
const { dispatch, maxUsers, navigation } = this.props;
|
||||
navigation.navigate('SelectedUsersViewCreateChannel', {
|
||||
nextAction: () => dispatch(createChannelRequest({ group: true })),
|
||||
buttonText: I18n.t('Create'),
|
||||
maxUsers
|
||||
});
|
||||
};
|
||||
|
||||
goRoom = (item: TGoRoomItem) => {
|
||||
logEvent(events.NEW_MSG_CHAT_WITH_USER);
|
||||
const { isMasterDetail, navigation } = this.props;
|
||||
if (isMasterDetail) {
|
||||
navigation.pop();
|
||||
}
|
||||
goRoom({ item, isMasterDetail });
|
||||
};
|
||||
|
||||
renderButton = ({ onPress, testID, title, icon, first }: IButton) => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<Touch onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }} testID={testID}>
|
||||
<View
|
||||
style={[
|
||||
first ? sharedStyles.separatorVertical : sharedStyles.separatorBottom,
|
||||
styles.button,
|
||||
{ borderColor: themes[theme].separatorColor }
|
||||
]}
|
||||
>
|
||||
<CustomIcon name={icon} size={24} color={themes[theme].tintColor} style={styles.buttonIcon} />
|
||||
<Text style={[styles.buttonText, { color: themes[theme].tintColor }]}>{title}</Text>
|
||||
</View>
|
||||
</Touch>
|
||||
);
|
||||
};
|
||||
|
||||
createDiscussion = () => {
|
||||
logEvent(events.NEW_MSG_CREATE_DISCUSSION);
|
||||
Navigation.navigate('CreateDiscussionView');
|
||||
};
|
||||
|
||||
handleHasPermission = async () => {
|
||||
const {
|
||||
createTeamPermission,
|
||||
createDirectMessagePermission,
|
||||
createPublicChannelPermission,
|
||||
createPrivateChannelPermission,
|
||||
createDiscussionPermission
|
||||
} = this.props;
|
||||
const permissions = [
|
||||
createPublicChannelPermission,
|
||||
createPrivateChannelPermission,
|
||||
createTeamPermission,
|
||||
createDirectMessagePermission,
|
||||
createDiscussionPermission
|
||||
];
|
||||
const permissionsToCreate = await hasPermission(permissions);
|
||||
this.setState({ permissions: permissionsToCreate });
|
||||
};
|
||||
|
||||
renderHeader = () => {
|
||||
const { maxUsers, theme, serverVersion } = this.props;
|
||||
const { permissions } = this.state;
|
||||
|
||||
return (
|
||||
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
|
||||
<SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='new-message-view-search' />
|
||||
<View style={styles.buttonContainer}>
|
||||
{permissions[0] || permissions[1]
|
||||
? this.renderButton({
|
||||
onPress: this.createChannel,
|
||||
title: I18n.t('Create_Channel'),
|
||||
icon: 'channel-public',
|
||||
testID: 'new-message-view-create-channel',
|
||||
first: true
|
||||
})
|
||||
: null}
|
||||
{compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && permissions[2]
|
||||
? this.renderButton({
|
||||
onPress: this.createTeam,
|
||||
title: I18n.t('Create_Team'),
|
||||
icon: 'teams',
|
||||
testID: 'new-message-view-create-team'
|
||||
})
|
||||
: null}
|
||||
{maxUsers > 2 && permissions[3]
|
||||
? this.renderButton({
|
||||
onPress: this.createGroupChat,
|
||||
title: I18n.t('Create_Direct_Messages'),
|
||||
icon: 'message',
|
||||
testID: 'new-message-view-create-direct-message'
|
||||
})
|
||||
: null}
|
||||
{permissions[4]
|
||||
? this.renderButton({
|
||||
onPress: this.createDiscussion,
|
||||
title: I18n.t('Create_Discussion'),
|
||||
icon: 'discussions',
|
||||
testID: 'new-message-view-create-discussion'
|
||||
})
|
||||
: null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderItem = ({ item, index }: { item: ISearch | TSubscriptionModel; index: number }) => {
|
||||
const { search, chats } = this.state;
|
||||
const { theme } = this.props;
|
||||
|
||||
let style = { borderColor: themes[theme].separatorColor };
|
||||
if (index === 0) {
|
||||
style = { ...style, ...sharedStyles.separatorTop };
|
||||
}
|
||||
if (search.length > 0 && index === search.length - 1) {
|
||||
style = { ...style, ...sharedStyles.separatorBottom };
|
||||
}
|
||||
if (search.length === 0 && index === chats.length - 1) {
|
||||
style = { ...style, ...sharedStyles.separatorBottom };
|
||||
}
|
||||
|
||||
const itemSearch = item as ISearch;
|
||||
const itemModel = item as TSubscriptionModel;
|
||||
|
||||
return (
|
||||
<UserItem
|
||||
name={itemSearch.search ? itemSearch.name : itemModel.fname || ''}
|
||||
username={itemSearch.search ? itemSearch.username : itemModel.name}
|
||||
onPress={() => this.goRoom(itemModel)}
|
||||
testID={`new-message-view-item-${item.name}`}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderList = () => {
|
||||
const { search, chats } = this.state;
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<FlatList
|
||||
data={search.length > 0 ? search : chats}
|
||||
extraData={this.state}
|
||||
keyExtractor={item => item._id || item.rid}
|
||||
ListHeaderComponent={this.renderHeader}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SafeAreaView testID='new-message-view'>
|
||||
<StatusBar />
|
||||
{this.renderList()}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
serverVersion: state.server.version as string,
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
maxUsers: (state.settings.DirectMesssage_maxUsers as number) || 1,
|
||||
createTeamPermission: state.permissions['create-team'],
|
||||
createDirectMessagePermission: state.permissions['create-d'],
|
||||
createPublicChannelPermission: state.permissions['create-c'],
|
||||
createPrivateChannelPermission: state.permissions['create-p'],
|
||||
createDiscussionPermission: state.permissions['start-discussion']
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(NewMessageView));
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
|
||||
import * as List from '../../containers/List';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
interface IButton {
|
||||
onPress: () => void;
|
||||
testID: string;
|
||||
title: string;
|
||||
icon: TIconsName;
|
||||
}
|
||||
|
||||
const ButtonCreate = ({ onPress, testID, title, icon }: IButton) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<List.Item
|
||||
onPress={onPress}
|
||||
testID={testID}
|
||||
left={() => <CustomIcon name={icon} size={24} color={themes[theme].bodyText} />}
|
||||
right={() => <CustomIcon name={'chevron-right'} size={24} color={themes[theme].bodyText} />}
|
||||
title={title}
|
||||
/>
|
||||
<List.Separator />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ButtonCreate;
|
|
@ -0,0 +1,107 @@
|
|||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import React, { useCallback } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
import { createChannelRequest } from '../../actions/createChannel';
|
||||
import { themes } from '../../lib/constants';
|
||||
import SearchBox from '../../containers/SearchBox';
|
||||
import I18n from '../../i18n';
|
||||
import Navigation from '../../lib/navigation/appNavigation';
|
||||
import { useTheme } from '../../theme';
|
||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import { NewMessageStackParamList } from '../../stacks/types';
|
||||
import { compareServerVersion } from '../../lib/methods/helpers';
|
||||
import { useAppSelector, usePermissions } from '../../lib/hooks';
|
||||
import ButtonCreate from './ButtonCreate';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingTop: 16
|
||||
},
|
||||
buttonContainer: {
|
||||
paddingBottom: 16
|
||||
}
|
||||
});
|
||||
|
||||
const HeaderNewMessage = ({ maxUsers, onChangeText }: { maxUsers: number; onChangeText: (text: string) => void }) => {
|
||||
const navigation = useNavigation<StackNavigationProp<NewMessageStackParamList, 'NewMessageView'>>();
|
||||
const dispatch = useDispatch();
|
||||
const { theme } = useTheme();
|
||||
|
||||
const serverVersion = useAppSelector(state => state.server.version as string);
|
||||
|
||||
const [
|
||||
createPublicChannelPermission,
|
||||
createPrivateChannelPermission,
|
||||
createTeamPermission,
|
||||
createDirectMessagePermission,
|
||||
createDiscussionPermission
|
||||
] = usePermissions(['create-c', 'create-p', 'create-team', 'create-d', 'start-discussion']);
|
||||
|
||||
const createChannel = useCallback(() => {
|
||||
logEvent(events.NEW_MSG_CREATE_CHANNEL);
|
||||
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
|
||||
}, [navigation]);
|
||||
|
||||
const createTeam = useCallback(() => {
|
||||
logEvent(events.NEW_MSG_CREATE_TEAM);
|
||||
navigation.navigate('SelectedUsersViewCreateChannel', {
|
||||
nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true })
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
const createGroupChat = useCallback(() => {
|
||||
logEvent(events.NEW_MSG_CREATE_GROUP_CHAT);
|
||||
navigation.navigate('SelectedUsersViewCreateChannel', {
|
||||
nextAction: () => dispatch(createChannelRequest({ group: true })),
|
||||
buttonText: I18n.t('Create'),
|
||||
maxUsers
|
||||
});
|
||||
}, [dispatch, maxUsers, navigation]);
|
||||
|
||||
const createDiscussion = useCallback(() => {
|
||||
logEvent(events.NEW_MSG_CREATE_DISCUSSION);
|
||||
Navigation.navigate('CreateDiscussionView');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={[styles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}>
|
||||
<View style={styles.buttonContainer}>
|
||||
{createPublicChannelPermission || createPrivateChannelPermission ? (
|
||||
<ButtonCreate
|
||||
onPress={createChannel}
|
||||
title={'Channel'}
|
||||
icon={'channel-public'}
|
||||
testID={'new-message-view-create-channel'}
|
||||
/>
|
||||
) : null}
|
||||
{compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && createTeamPermission ? (
|
||||
<ButtonCreate onPress={createTeam} title={'Team'} icon={'teams'} testID={'new-message-view-create-team'} />
|
||||
) : null}
|
||||
{maxUsers > 2 && createDirectMessagePermission ? (
|
||||
<ButtonCreate
|
||||
onPress={createGroupChat}
|
||||
title={'Direct_message'}
|
||||
icon={'message'}
|
||||
testID={'new-message-view-create-direct-message'}
|
||||
/>
|
||||
) : null}
|
||||
{createDiscussionPermission ? (
|
||||
<ButtonCreate
|
||||
onPress={createDiscussion}
|
||||
title={'Discussion'}
|
||||
icon={'discussions'}
|
||||
testID={'new-message-view-create-discussion'}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
<SearchBox onChangeText={(text: string) => onChangeText(text)} testID='new-message-view-search' />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderNewMessage;
|
|
@ -0,0 +1,115 @@
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
import { shallowEqual } from 'react-redux';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import * as List from '../../containers/List';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { ISearch, TSubscriptionModel } from '../../definitions';
|
||||
import I18n from '../../i18n';
|
||||
import database from '../../lib/database';
|
||||
import { useTheme } from '../../theme';
|
||||
import { goRoom as goRoomMethod, TGoRoomItem } from '../../lib/methods/helpers/goRoom';
|
||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import { NewMessageStackParamList } from '../../stacks/types';
|
||||
import { search as searchMethod } from '../../lib/methods';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import UserItem from '../../containers/UserItem';
|
||||
import HeaderNewMessage from './HeaderNewMessage';
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
|
||||
type TItem = ISearch | TSubscriptionModel;
|
||||
|
||||
const NewMessageView = () => {
|
||||
const [chats, setChats] = useState<TSubscriptionModel[]>([]);
|
||||
const [search, setSearch] = useState<TItem[]>([]);
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const navigation = useNavigation<StackNavigationProp<NewMessageStackParamList, 'NewMessageView'>>();
|
||||
|
||||
const { isMasterDetail, maxUsers, useRealName } = useAppSelector(
|
||||
state => ({
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
maxUsers: (state.settings.DirectMesssage_maxUsers as number) || 1,
|
||||
useRealName: state.settings.UI_Use_Real_Name as boolean
|
||||
}),
|
||||
shallowEqual
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />,
|
||||
title: I18n.t('Create_New')
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
try {
|
||||
const db = database.active;
|
||||
const c = await db
|
||||
.get('subscriptions')
|
||||
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
|
||||
.fetch();
|
||||
setChats(c);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (text: string) => {
|
||||
const result = (await searchMethod({ text, filterRooms: false })) as ISearch[];
|
||||
setSearch(result);
|
||||
}, []);
|
||||
|
||||
const goRoom = useCallback(
|
||||
(item: TGoRoomItem) => {
|
||||
logEvent(events.NEW_MSG_CHAT_WITH_USER);
|
||||
|
||||
if (isMasterDetail) {
|
||||
navigation.pop();
|
||||
}
|
||||
goRoomMethod({ item, isMasterDetail });
|
||||
},
|
||||
[isMasterDetail, navigation]
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView testID='new-message-view'>
|
||||
<StatusBar />
|
||||
<FlatList
|
||||
data={search.length > 0 ? search : chats}
|
||||
keyExtractor={item => item._id || item.rid}
|
||||
ListHeaderComponent={<HeaderNewMessage maxUsers={maxUsers} onChangeText={handleSearch} />}
|
||||
renderItem={({ item }) => {
|
||||
const itemSearch = item as ISearch;
|
||||
const itemModel = item as TSubscriptionModel;
|
||||
|
||||
return (
|
||||
<UserItem
|
||||
name={useRealName && itemSearch.fname ? itemSearch.fname : itemModel.name}
|
||||
username={itemSearch.search ? itemSearch.username : itemModel.name}
|
||||
onPress={() => goRoom(itemModel)}
|
||||
testID={`new-message-view-item-${item.name}`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
ListFooterComponent={List.Separator}
|
||||
contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewMessageView;
|
|
@ -705,7 +705,7 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
|||
) : null}
|
||||
{room.broadcast
|
||||
? [
|
||||
<Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>,
|
||||
<Text style={styles.broadcast}>{I18n.t('Broadcast')}</Text>,
|
||||
<View style={[styles.divider, { borderColor: themes[theme].separatorColor }]} />
|
||||
]
|
||||
: null}
|
||||
|
|
|
@ -24,8 +24,8 @@ const Channel = ({ room }: { room: ISubscription }) => {
|
|||
testID='room-info-view-announcement'
|
||||
/>
|
||||
<Item
|
||||
label={I18n.t('Broadcast_Channel')}
|
||||
content={room.broadcast ? I18n.t('Broadcast_channel_Description') : ''}
|
||||
label={I18n.t('Broadcast')}
|
||||
content={room.broadcast ? I18n.t('Broadcast_hint') : ''}
|
||||
testID='room-info-view-broadcast'
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -120,6 +120,7 @@ const removeFromTeam = async (
|
|||
updateState({
|
||||
members: newMembers
|
||||
});
|
||||
appNavigation.navigate('RoomMembersView', { room });
|
||||
}
|
||||
} catch (e: any) {
|
||||
log(e);
|
||||
|
|
|
@ -1,291 +0,0 @@
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import React from 'react';
|
||||
import { FlatList, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { addUser, removeUser, reset } from '../actions/selectedUsers';
|
||||
import { themes } from '../lib/constants';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import * as List from '../containers/List';
|
||||
import { sendLoadingEvent } from '../containers/Loading';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import SearchBox from '../containers/SearchBox';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { IApplicationState, IBaseScreen, ISearch, ISearchLocal, IUser } from '../definitions';
|
||||
import I18n from '../i18n';
|
||||
import database from '../lib/database';
|
||||
import UserItem from '../containers/UserItem';
|
||||
import { ISelectedUser } from '../reducers/selectedUsers';
|
||||
import { getUserSelector } from '../selectors/login';
|
||||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import { withTheme } from '../theme';
|
||||
import { showErrorAlert } from '../lib/methods/helpers/info';
|
||||
import log, { events, logEvent } from '../lib/methods/helpers/log';
|
||||
import sharedStyles from './Styles';
|
||||
import { search } from '../lib/methods';
|
||||
import { isGroupChat } from '../lib/methods/helpers';
|
||||
|
||||
const ITEM_WIDTH = 250;
|
||||
const getItemLayout = (_: any, index: number) => ({ length: ITEM_WIDTH, offset: ITEM_WIDTH * index, index });
|
||||
|
||||
interface ISelectedUsersViewState {
|
||||
maxUsers?: number;
|
||||
search: (ISearch | ISearchLocal)[];
|
||||
chats: ISelectedUser[];
|
||||
}
|
||||
|
||||
interface ISelectedUsersViewProps extends IBaseScreen<ChatsStackParamList, 'SelectedUsersView'> {
|
||||
users: ISelectedUser[];
|
||||
loading: boolean;
|
||||
user: IUser;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelectedUsersViewState> {
|
||||
private flatlist?: FlatList;
|
||||
|
||||
private querySubscription?: Subscription;
|
||||
|
||||
constructor(props: ISelectedUsersViewProps) {
|
||||
super(props);
|
||||
this.init();
|
||||
const maxUsers = props.route.params?.maxUsers;
|
||||
this.state = {
|
||||
maxUsers,
|
||||
search: [],
|
||||
chats: []
|
||||
};
|
||||
const { user, dispatch } = this.props;
|
||||
if (this.isGroupChat()) {
|
||||
dispatch(addUser({ _id: user.id, name: user.username, fname: user.name as string }));
|
||||
}
|
||||
this.setHeader(props.route.params?.showButton);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ISelectedUsersViewProps) {
|
||||
const { users, loading } = this.props;
|
||||
if (this.isGroupChat()) {
|
||||
if (prevProps.users.length !== users.length) {
|
||||
this.setHeader(users.length > 0);
|
||||
}
|
||||
}
|
||||
if (loading !== prevProps.loading) {
|
||||
sendLoadingEvent({ visible: loading });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(reset());
|
||||
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||
this.querySubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
// showButton can be sent as route params or updated by the component
|
||||
setHeader = (showButton?: boolean) => {
|
||||
const { navigation, route } = this.props;
|
||||
const title = route.params?.title ?? I18n.t('Select_Users');
|
||||
const buttonText = route.params?.buttonText ?? I18n.t('Next');
|
||||
const maxUsers = route.params?.maxUsers;
|
||||
const nextAction = route.params?.nextAction ?? (() => {});
|
||||
const options = {
|
||||
title,
|
||||
headerRight: () =>
|
||||
(!maxUsers || showButton) && (
|
||||
<HeaderButton.Container>
|
||||
<HeaderButton.Item title={buttonText} onPress={nextAction} testID='selected-users-view-submit' />
|
||||
</HeaderButton.Container>
|
||||
)
|
||||
};
|
||||
navigation.setOptions(options);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
init = async () => {
|
||||
try {
|
||||
const db = database.active;
|
||||
const observable = await db.get('subscriptions').query(Q.where('t', 'd')).observeWithColumns(['room_updated_at']);
|
||||
|
||||
this.querySubscription = observable.subscribe(data => {
|
||||
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']) as ISelectedUser[];
|
||||
this.setState({ chats });
|
||||
});
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
onSearchChangeText(text: string) {
|
||||
this.handleSearch(text);
|
||||
}
|
||||
|
||||
handleSearch = async (text: string) => {
|
||||
const result = await search({ text, filterRooms: false });
|
||||
this.setState({
|
||||
search: result
|
||||
});
|
||||
};
|
||||
|
||||
isGroupChat = () => {
|
||||
const { maxUsers } = this.state;
|
||||
return maxUsers && maxUsers > 2;
|
||||
};
|
||||
|
||||
isChecked = (username: string) => {
|
||||
const { users } = this.props;
|
||||
return users.findIndex(el => el.name === username) !== -1;
|
||||
};
|
||||
|
||||
toggleUser = (user: ISelectedUser) => {
|
||||
const { maxUsers } = this.state;
|
||||
const {
|
||||
dispatch,
|
||||
users,
|
||||
user: { username }
|
||||
} = this.props;
|
||||
|
||||
// Disallow removing self user from the direct message group
|
||||
if (this.isGroupChat() && username === user.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isChecked(user.name)) {
|
||||
if (this.isGroupChat() && users.length === maxUsers) {
|
||||
return showErrorAlert(I18n.t('Max_number_of_users_allowed_is_number', { maxUsers }), I18n.t('Oops'));
|
||||
}
|
||||
logEvent(events.SELECTED_USERS_ADD_USER);
|
||||
dispatch(addUser(user));
|
||||
} else {
|
||||
logEvent(events.SELECTED_USERS_REMOVE_USER);
|
||||
dispatch(removeUser(user));
|
||||
}
|
||||
};
|
||||
|
||||
_onPressItem = (id: string, item = {} as ISelectedUser) => {
|
||||
if (item.search) {
|
||||
this.toggleUser({ _id: item._id, name: item.username as string, fname: item.name });
|
||||
} else {
|
||||
this.toggleUser({ _id: item._id, name: item.name, fname: item.fname });
|
||||
}
|
||||
};
|
||||
|
||||
_onPressSelectedItem = (item: ISelectedUser) => this.toggleUser(item);
|
||||
|
||||
renderHeader = () => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<View style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
<SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='select-users-view-search' />
|
||||
{this.renderSelected()}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
setFlatListRef = (ref: FlatList) => (this.flatlist = ref);
|
||||
|
||||
onContentSizeChange = () => this.flatlist?.scrollToEnd({ animated: true });
|
||||
|
||||
renderSelected = () => {
|
||||
const { users, theme } = this.props;
|
||||
|
||||
if (users.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={users}
|
||||
ref={this.setFlatListRef}
|
||||
onContentSizeChange={this.onContentSizeChange}
|
||||
getItemLayout={getItemLayout}
|
||||
keyExtractor={item => item._id}
|
||||
style={[sharedStyles.separatorTop, { borderColor: themes[theme].separatorColor }]}
|
||||
contentContainerStyle={{ marginVertical: 5 }}
|
||||
renderItem={this.renderSelectedItem}
|
||||
keyboardShouldPersistTaps='always'
|
||||
horizontal
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderSelectedItem = ({ item }: { item: ISelectedUser }) => (
|
||||
<UserItem
|
||||
name={item.fname}
|
||||
username={item.name}
|
||||
onPress={() => this._onPressSelectedItem(item)}
|
||||
testID={`selected-user-${item.name}`}
|
||||
style={{ paddingRight: 15 }}
|
||||
/>
|
||||
);
|
||||
|
||||
renderItem = ({ item, index }: { item: ISelectedUser; index: number }) => {
|
||||
const { search, chats } = this.state;
|
||||
const { theme } = this.props;
|
||||
|
||||
const name = item.search ? item.name : item.fname;
|
||||
const username = item.search ? (item.username as string) : item.name;
|
||||
let style = { borderColor: themes[theme].separatorColor };
|
||||
if (index === 0) {
|
||||
style = { ...style, ...sharedStyles.separatorTop };
|
||||
}
|
||||
if (search.length > 0 && index === search.length - 1) {
|
||||
style = { ...style, ...sharedStyles.separatorBottom };
|
||||
}
|
||||
if (search.length === 0 && index === chats.length - 1) {
|
||||
style = { ...style, ...sharedStyles.separatorBottom };
|
||||
}
|
||||
return (
|
||||
<UserItem
|
||||
name={name}
|
||||
username={username}
|
||||
onPress={() => this._onPressItem(item._id, item)}
|
||||
testID={`select-users-view-item-${item.name}`}
|
||||
icon={this.isChecked(username) ? 'check' : null}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderList = () => {
|
||||
const { search, chats } = this.state;
|
||||
const { theme } = this.props;
|
||||
|
||||
const searchOrChats = (search.length > 0 ? search : chats) as ISelectedUser[];
|
||||
// filter DM between multiple users
|
||||
const data = searchOrChats.filter(sub => !isGroupChat(sub));
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={data}
|
||||
extraData={this.props}
|
||||
keyExtractor={item => item._id}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
ListHeaderComponent={this.renderHeader}
|
||||
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SafeAreaView testID='select-users-view'>
|
||||
<StatusBar />
|
||||
{this.renderList()}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
baseUrl: state.server.server,
|
||||
users: state.selectedUsers.users,
|
||||
loading: state.selectedUsers.loading,
|
||||
user: getUserSelector(state)
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(SelectedUsersView));
|
|
@ -0,0 +1,74 @@
|
|||
import React, { useRef } from 'react';
|
||||
import { FlatList, View, Text, StyleSheet } from 'react-native';
|
||||
|
||||
import { themes } from '../../lib/constants';
|
||||
import SearchBox from '../../containers/SearchBox';
|
||||
import I18n from '../../i18n';
|
||||
import { ISelectedUser } from '../../reducers/selectedUsers';
|
||||
import { useTheme } from '../../theme';
|
||||
import sharedStyles from '../Styles';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import Chip from '../../containers/Chip';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
selectedText: {
|
||||
marginLeft: 16,
|
||||
marginBottom: 12,
|
||||
fontSize: 12,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
contentContainerList: {
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 16
|
||||
}
|
||||
});
|
||||
|
||||
const Header = ({
|
||||
onChangeText,
|
||||
useRealName,
|
||||
onPressItem
|
||||
}: {
|
||||
useRealName: boolean;
|
||||
onChangeText: (text: string) => void;
|
||||
onPressItem: (userItem: ISelectedUser) => void;
|
||||
}) => {
|
||||
const flatlist = useRef<FlatList>();
|
||||
const { theme } = useTheme();
|
||||
const { users } = useAppSelector(state => ({
|
||||
users: state.selectedUsers.users
|
||||
}));
|
||||
|
||||
const onContentSizeChange = () => flatlist?.current?.scrollToEnd({ animated: true });
|
||||
|
||||
return (
|
||||
<View style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
<SearchBox onChangeText={(text: string) => onChangeText(text)} testID='select-users-view-search' />
|
||||
{users.length === 0 ? null : (
|
||||
<View>
|
||||
<Text style={[styles.selectedText, { color: themes[theme].auxiliaryTintColor }]}>
|
||||
{I18n.t('N_Selected_members', { n: users.length })}
|
||||
</Text>
|
||||
<FlatList
|
||||
data={users}
|
||||
ref={(ref: FlatList) => (flatlist.current = ref)}
|
||||
onContentSizeChange={onContentSizeChange}
|
||||
keyExtractor={item => item._id}
|
||||
renderItem={({ item }) => {
|
||||
const name = useRealName && item.fname ? item.fname : item.name;
|
||||
const username = item.search ? (item.username as string) : item.name;
|
||||
|
||||
return (
|
||||
<Chip text={name} avatar={username} onPress={() => onPressItem(item)} testID={`selected-user-${item.name}`} />
|
||||
);
|
||||
}}
|
||||
keyboardShouldPersistTaps='always'
|
||||
contentContainerStyle={styles.contentContainerList}
|
||||
horizontal
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
|
@ -0,0 +1,180 @@
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
import { shallowEqual, useDispatch } from 'react-redux';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import { addUser, removeUser, reset } from '../../actions/selectedUsers';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import * as List from '../../containers/List';
|
||||
import { sendLoadingEvent } from '../../containers/Loading';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { ISearch, ISearchLocal } from '../../definitions';
|
||||
import I18n from '../../i18n';
|
||||
import database from '../../lib/database';
|
||||
import UserItem from '../../containers/UserItem';
|
||||
import { ISelectedUser } from '../../reducers/selectedUsers';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { useTheme } from '../../theme';
|
||||
import { showErrorAlert } from '../../lib/methods/helpers/info';
|
||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import { search as searchMethod } from '../../lib/methods';
|
||||
import { isGroupChat as isGroupChatMethod } from '../../lib/methods/helpers';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import Header from './Header';
|
||||
|
||||
type TRoute = RouteProp<ChatsStackParamList, 'SelectedUsersView'>;
|
||||
type TNavigation = StackNavigationProp<ChatsStackParamList, 'SelectedUsersView'>;
|
||||
|
||||
type TSearchItem = ISearch | ISearchLocal;
|
||||
|
||||
const SelectedUsersView = () => {
|
||||
const [chats, setChats] = useState<ISelectedUser[]>([]);
|
||||
const [search, setSearch] = useState<TSearchItem[]>([]);
|
||||
|
||||
const { maxUsers, showButton, title, buttonText, nextAction } = useRoute<TRoute>().params;
|
||||
const navigation = useNavigation<TNavigation>();
|
||||
|
||||
const { colors } = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { users, loading, useRealName, user } = useAppSelector(
|
||||
state => ({
|
||||
users: state.selectedUsers.users,
|
||||
loading: state.selectedUsers.loading,
|
||||
useRealName: state.settings.UI_Use_Real_Name as boolean,
|
||||
user: getUserSelector(state)
|
||||
}),
|
||||
shallowEqual
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
sendLoadingEvent({ visible: loading });
|
||||
}, [loading]);
|
||||
|
||||
const isChecked = (username: string) => users.findIndex(el => el.name === username) !== -1;
|
||||
|
||||
const isGroupChat = () => maxUsers && maxUsers > 2;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const titleHeader = title ?? I18n.t('Select_Members');
|
||||
const buttonTextHeader = buttonText ?? I18n.t('Next');
|
||||
const nextActionHeader = nextAction ?? (() => {});
|
||||
const options = {
|
||||
title: titleHeader,
|
||||
headerRight: () =>
|
||||
(!maxUsers || showButton || (isGroupChat() && users.length > 1)) && (
|
||||
<HeaderButton.Container>
|
||||
<HeaderButton.Item
|
||||
title={users.length > 0 ? buttonTextHeader : I18n.t('Skip')}
|
||||
onPress={nextActionHeader}
|
||||
testID='selected-users-view-submit'
|
||||
/>
|
||||
</HeaderButton.Container>
|
||||
)
|
||||
};
|
||||
navigation.setOptions(options);
|
||||
}, [navigation, users.length, maxUsers]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isGroupChat()) {
|
||||
dispatch(addUser({ _id: user.id, name: user.username, fname: user.name as string }));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let querySubscription: Subscription;
|
||||
const init = async () => {
|
||||
try {
|
||||
const db = database.active;
|
||||
const observable = await db.get('subscriptions').query(Q.where('t', 'd')).observeWithColumns(['room_updated_at']);
|
||||
|
||||
querySubscription = observable.subscribe(data => {
|
||||
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']) as ISelectedUser[];
|
||||
setChats(chats);
|
||||
});
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
init();
|
||||
|
||||
return () => {
|
||||
dispatch(reset());
|
||||
if (querySubscription && querySubscription.unsubscribe) {
|
||||
querySubscription.unsubscribe();
|
||||
}
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const handleSearch = useCallback(async (text: string) => {
|
||||
const result = await searchMethod({ text, filterRooms: false });
|
||||
setSearch(result);
|
||||
}, []);
|
||||
|
||||
const toggleUser = (userItem: ISelectedUser) => {
|
||||
// Disallow removing self user from the direct message group
|
||||
if (isGroupChat() && user.username === userItem.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isChecked(userItem.name)) {
|
||||
if (isGroupChat() && users.length === maxUsers) {
|
||||
return showErrorAlert(I18n.t('Max_number_of_users_allowed_is_number', { maxUsers }), I18n.t('Oops'));
|
||||
}
|
||||
logEvent(events.SELECTED_USERS_ADD_USER);
|
||||
dispatch(addUser(userItem));
|
||||
} else {
|
||||
logEvent(events.SELECTED_USERS_REMOVE_USER);
|
||||
dispatch(removeUser(userItem));
|
||||
}
|
||||
};
|
||||
|
||||
const _onPressItem = (item = {} as ISelectedUser) => {
|
||||
if (item.search) {
|
||||
toggleUser({ _id: item._id, name: item.username as string, fname: item.name });
|
||||
} else {
|
||||
toggleUser({ _id: item._id, name: item.name, fname: item.fname });
|
||||
}
|
||||
};
|
||||
|
||||
const searchOrChats = (search.length > 0 ? search : chats) as ISelectedUser[];
|
||||
// filter DM between multiple users
|
||||
const data = searchOrChats.filter(sub => !isGroupChatMethod(sub));
|
||||
|
||||
return (
|
||||
<SafeAreaView testID='select-users-view'>
|
||||
<StatusBar />
|
||||
<FlatList
|
||||
data={data}
|
||||
keyExtractor={item => item._id}
|
||||
renderItem={({ item }) => {
|
||||
const name = useRealName && item.fname ? item.fname : item.name;
|
||||
const username = item.search ? (item.username as string) : item.name;
|
||||
return (
|
||||
<UserItem
|
||||
name={name}
|
||||
username={username}
|
||||
onPress={() => _onPressItem(item)}
|
||||
testID={`select-users-view-item-${item.name}`}
|
||||
icon={isChecked(username) ? 'checkbox-checked' : 'checkbox-unchecked'}
|
||||
iconColor={isChecked(username) ? colors.actionTintColor : colors.separatorColor}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
ListFooterComponent={<List.Separator />}
|
||||
ListHeaderComponent={<Header useRealName={useRealName} onChangeText={handleSearch} onPressItem={toggleUser} />}
|
||||
contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectedUsersView;
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
50
yarn.lock
50
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"
|
||||
|
|
Loading…
Reference in New Issue