[NEW] Create discussions (#1942)
* [WIP][NEW] Create Discussion * [FIX] Clear multiselect & Translations * [NEW] Create Discussion at MessageActions * [NEW] Disabled Multiselect * [FIX] Initial channel * [NEW] Create discussion on MessageBox Actions * [FIX] Crashing on edit name * [IMPROVEMENT] New message layout * [CHORE] Update README * [NEW] Avatars on MultiSelect * [FIX] Select Users * [FIX] Add redirect and Handle tablet * [IMPROVEMENT] Split CreateDiscussionView * [FIX] Create a discussion inner discussion * [FIX] Create a discussion * [I18N] Add pt-br * Change icons * [FIX] Nav to discussion & header title * Fix header Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
acdf39b32d
commit
475ccbd9c7
|
@ -80,7 +80,7 @@ Readme will guide you on how to config.
|
|||
|--------------------------------------------------------------- |-------- |
|
||||
| Jitsi Integration | ✅ |
|
||||
| Federation (Directory) | ✅ |
|
||||
| Discussions | ❌ |
|
||||
| Discussions | ✅ |
|
||||
| Omnichannel | ❌ |
|
||||
| Threads | ✅ |
|
||||
| Record Audio | ✅ |
|
||||
|
|
|
@ -35,6 +35,7 @@ export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'DELETE', 'REMOVED', 'U
|
|||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);
|
||||
export const SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER', 'REMOVE_USER', 'RESET', 'SET_LOADING']);
|
||||
export const SERVER = createRequestTypes('SERVER', [
|
||||
...defaultTypes,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function createDiscussionRequest(data) {
|
||||
return {
|
||||
type: types.CREATE_DISCUSSION.REQUEST,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
export function createDiscussionSuccess(data) {
|
||||
return {
|
||||
type: types.CREATE_DISCUSSION.SUCCESS,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
export function createDiscussionFailure(err) {
|
||||
return {
|
||||
type: types.CREATE_DISCUSSION.FAILURE,
|
||||
err
|
||||
};
|
||||
}
|
|
@ -4,10 +4,7 @@ import { View } from 'react-native';
|
|||
import FastImage from 'react-native-fast-image';
|
||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
import Touch from '../utils/touch';
|
||||
|
||||
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
|
||||
`${ baseUrl }${ url }?format=png&size=${ uriSize }&${ avatarAuthURLFragment }`
|
||||
);
|
||||
import { avatarURL } from '../utils/avatar';
|
||||
|
||||
const Avatar = React.memo(({
|
||||
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress, theme
|
||||
|
@ -22,24 +19,9 @@ const Avatar = React.memo(({
|
|||
return null;
|
||||
}
|
||||
|
||||
const room = type === 'd' ? text : `@${ text }`;
|
||||
|
||||
// Avoid requesting several sizes by having only two sizes on cache
|
||||
const uriSize = size === 100 ? 100 : 50;
|
||||
|
||||
let avatarAuthURLFragment = '';
|
||||
if (userId && token) {
|
||||
avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`;
|
||||
}
|
||||
|
||||
|
||||
let uri;
|
||||
if (avatar) {
|
||||
uri = avatar.includes('http') ? avatar : formatUrl(avatar, baseUrl, uriSize, avatarAuthURLFragment);
|
||||
} else {
|
||||
uri = formatUrl(`/avatar/${ room }`, baseUrl, uriSize, avatarAuthURLFragment);
|
||||
}
|
||||
|
||||
const uri = avatarURL({
|
||||
type, text, size, userId, token, avatar, baseUrl
|
||||
});
|
||||
|
||||
let image = (
|
||||
<FastImage
|
||||
|
|
|
@ -63,6 +63,10 @@ class MessageActions extends React.Component {
|
|||
this.EDIT_INDEX = this.options.length - 1;
|
||||
}
|
||||
|
||||
// Create Discussion
|
||||
this.options.push(I18n.t('Create_Discussion'));
|
||||
this.CREATE_DISCUSSION_INDEX = this.options.length - 1;
|
||||
|
||||
// Mark as unread
|
||||
if (message.u && message.u._id !== user.id) {
|
||||
this.options.push(I18n.t('Mark_unread'));
|
||||
|
@ -371,6 +375,11 @@ class MessageActions extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleCreateDiscussion = () => {
|
||||
const { message, room: channel } = this.props;
|
||||
Navigation.navigate('CreateDiscussionView', { message, channel });
|
||||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
if (actionIndex) {
|
||||
switch (actionIndex) {
|
||||
|
@ -413,6 +422,9 @@ class MessageActions extends React.Component {
|
|||
case this.READ_RECEIPT_INDEX:
|
||||
this.handleReadReceipt();
|
||||
break;
|
||||
case this.CREATE_DISCUSSION_INDEX:
|
||||
this.handleCreateDiscussion();
|
||||
break;
|
||||
case this.TOGGLE_TRANSLATION_INDEX:
|
||||
this.handleToggleTranslation();
|
||||
break;
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { CancelEditingButton, FileButton } from './buttons';
|
||||
import { CancelEditingButton, ActionsButton } from './buttons';
|
||||
|
||||
const LeftButtons = React.memo(({
|
||||
theme, showFileActions, editing, editCancel
|
||||
theme, showMessageBoxActions, editing, editCancel
|
||||
}) => {
|
||||
if (editing) {
|
||||
return <CancelEditingButton onPress={editCancel} theme={theme} />;
|
||||
}
|
||||
return <FileButton onPress={showFileActions} theme={theme} />;
|
||||
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
|
||||
});
|
||||
|
||||
LeftButtons.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
showFileActions: PropTypes.func.isRequired,
|
||||
showMessageBoxActions: PropTypes.func.isRequired,
|
||||
editing: PropTypes.bool,
|
||||
editCancel: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { SendButton, AudioButton, FileButton } from './buttons';
|
||||
import { SendButton, AudioButton, ActionsButton } from './buttons';
|
||||
|
||||
const RightButtons = React.memo(({
|
||||
theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled, showFileActions
|
||||
theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled, showMessageBoxActions
|
||||
}) => {
|
||||
if (showSend) {
|
||||
return <SendButton onPress={submit} theme={theme} />;
|
||||
|
@ -13,11 +13,11 @@ const RightButtons = React.memo(({
|
|||
return (
|
||||
<>
|
||||
<AudioButton onPress={recordAudioMessage} theme={theme} />
|
||||
<FileButton onPress={showFileActions} theme={theme} />
|
||||
<ActionsButton onPress={showMessageBoxActions} theme={theme} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <FileButton onPress={showFileActions} theme={theme} />;
|
||||
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
|
||||
});
|
||||
|
||||
RightButtons.propTypes = {
|
||||
|
@ -26,7 +26,7 @@ RightButtons.propTypes = {
|
|||
submit: PropTypes.func.isRequired,
|
||||
recordAudioMessage: PropTypes.func.isRequired,
|
||||
recordAudioMessageEnabled: PropTypes.bool,
|
||||
showFileActions: PropTypes.func.isRequired
|
||||
showMessageBoxActions: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default RightButtons;
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import BaseButton from './BaseButton';
|
||||
|
||||
const FileButton = React.memo(({ theme, onPress }) => (
|
||||
const ActionsButton = React.memo(({ theme, onPress }) => (
|
||||
<BaseButton
|
||||
onPress={onPress}
|
||||
testID='messagebox-actions'
|
||||
|
@ -13,9 +13,9 @@ const FileButton = React.memo(({ theme, onPress }) => (
|
|||
/>
|
||||
));
|
||||
|
||||
FileButton.propTypes = {
|
||||
ActionsButton.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
onPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default FileButton;
|
||||
export default ActionsButton;
|
|
@ -2,12 +2,12 @@ import CancelEditingButton from './CancelEditingButton';
|
|||
import ToggleEmojiButton from './ToggleEmojiButton';
|
||||
import SendButton from './SendButton';
|
||||
import AudioButton from './AudioButton';
|
||||
import FileButton from './FileButton';
|
||||
import ActionsButton from './ActionsButton';
|
||||
|
||||
export {
|
||||
CancelEditingButton,
|
||||
ToggleEmojiButton,
|
||||
SendButton,
|
||||
AudioButton,
|
||||
FileButton
|
||||
ActionsButton
|
||||
};
|
||||
|
|
|
@ -45,6 +45,7 @@ import {
|
|||
import CommandsPreview from './CommandsPreview';
|
||||
import { Review } from '../../utils/review';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import Navigation from '../../lib/Navigation';
|
||||
|
||||
const imagePickerConfig = {
|
||||
cropping: true,
|
||||
|
@ -65,6 +66,7 @@ const FILE_PHOTO_INDEX = 1;
|
|||
const FILE_VIDEO_INDEX = 2;
|
||||
const FILE_LIBRARY_INDEX = 3;
|
||||
const FILE_DOCUMENT_INDEX = 4;
|
||||
const CREATE_DISCUSSION_INDEX = 5;
|
||||
|
||||
class MessageBox extends Component {
|
||||
static propTypes = {
|
||||
|
@ -113,12 +115,13 @@ class MessageBox extends Component {
|
|||
};
|
||||
this.text = '';
|
||||
this.focused = false;
|
||||
this.fileOptions = [
|
||||
this.messageBoxActions = [
|
||||
I18n.t('Cancel'),
|
||||
I18n.t('Take_a_photo'),
|
||||
I18n.t('Take_a_video'),
|
||||
I18n.t('Choose_from_library'),
|
||||
I18n.t('Choose_file')
|
||||
I18n.t('Choose_file'),
|
||||
I18n.t('Create_Discussion')
|
||||
];
|
||||
const libPickerLabels = {
|
||||
cropperChooseText: I18n.t('Choose'),
|
||||
|
@ -157,8 +160,8 @@ class MessageBox extends Component {
|
|||
}
|
||||
} else {
|
||||
try {
|
||||
const room = await subsCollection.find(rid);
|
||||
msg = room.draftMessage;
|
||||
this.room = await subsCollection.find(rid);
|
||||
msg = this.room.draftMessage;
|
||||
} catch (error) {
|
||||
console.log('Messagebox.didMount: Room not found');
|
||||
}
|
||||
|
@ -588,20 +591,24 @@ class MessageBox extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
createDiscussion = () => {
|
||||
Navigation.navigate('CreateDiscussionView', { channel: this.room });
|
||||
}
|
||||
|
||||
showUploadModal = (file) => {
|
||||
this.setState({ file: { ...file, isVisible: true } });
|
||||
}
|
||||
|
||||
showFileActions = () => {
|
||||
showMessageBoxActions = () => {
|
||||
ActionSheet.showActionSheetWithOptions({
|
||||
options: this.fileOptions,
|
||||
options: this.messageBoxActions,
|
||||
cancelButtonIndex: FILE_CANCEL_INDEX
|
||||
}, (actionIndex) => {
|
||||
this.handleFileActionPress(actionIndex);
|
||||
this.handleMessageBoxActions(actionIndex);
|
||||
});
|
||||
}
|
||||
|
||||
handleFileActionPress = (actionIndex) => {
|
||||
handleMessageBoxActions = (actionIndex) => {
|
||||
switch (actionIndex) {
|
||||
case FILE_PHOTO_INDEX:
|
||||
this.takePhoto();
|
||||
|
@ -615,6 +622,9 @@ class MessageBox extends Component {
|
|||
case FILE_DOCUMENT_INDEX:
|
||||
this.chooseFile();
|
||||
break;
|
||||
case CREATE_DISCUSSION_INDEX:
|
||||
this.createDiscussion();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -783,7 +793,7 @@ class MessageBox extends Component {
|
|||
} else if (handleCommandSubmit(event)) {
|
||||
this.submit();
|
||||
} else if (handleCommandShowUpload(event)) {
|
||||
this.showFileActions();
|
||||
this.showMessageBoxActions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -828,7 +838,7 @@ class MessageBox extends Component {
|
|||
theme={theme}
|
||||
showEmojiKeyboard={showEmojiKeyboard}
|
||||
editing={editing}
|
||||
showFileActions={this.showFileActions}
|
||||
showMessageBoxActions={this.showMessageBoxActions}
|
||||
editCancel={this.editCancel}
|
||||
openEmoji={this.openEmoji}
|
||||
closeEmoji={this.closeEmoji}
|
||||
|
@ -854,7 +864,7 @@ class MessageBox extends Component {
|
|||
submit={this.submit}
|
||||
recordAudioMessage={this.recordAudioMessage}
|
||||
recordAudioMessageEnabled={Message_AudioRecorderEnabled}
|
||||
showFileActions={this.showFileActions}
|
||||
showMessageBoxActions={this.showMessageBoxActions}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import { Text, View, Image } from 'react-native';
|
||||
import { Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { textParser } from '../utils';
|
||||
|
@ -19,7 +20,7 @@ const Chip = ({ item, onSelect, theme }) => (
|
|||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
>
|
||||
<>
|
||||
{item.imageUrl ? <Image style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
||||
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
||||
<Text numberOfLines={1} style={[styles.chipText, { color: themes[theme].titleText }]}>{textParser([item.text])}</Text>
|
||||
<CustomIcon name='cross' size={16} color={themes[theme].auxiliaryText} />
|
||||
</>
|
||||
|
|
|
@ -9,12 +9,13 @@ import ActivityIndicator from '../../ActivityIndicator';
|
|||
import styles from './styles';
|
||||
|
||||
const Input = ({
|
||||
children, open, theme, loading
|
||||
children, open, theme, loading, inputStyle, disabled
|
||||
}) => (
|
||||
<Touchable
|
||||
onPress={() => open(true)}
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<View style={[styles.input, { borderColor: themes[theme].separatorColor }]}>
|
||||
{children}
|
||||
|
@ -30,6 +31,8 @@ Input.propTypes = {
|
|||
children: PropTypes.node,
|
||||
open: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
inputStyle: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
loading: PropTypes.bool
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { Text, FlatList } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import Separator from '../../Separator';
|
||||
import Check from '../../Check';
|
||||
|
@ -26,6 +27,7 @@ const Item = ({
|
|||
]}
|
||||
>
|
||||
<>
|
||||
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
|
||||
<Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text>
|
||||
{selected ? <Check theme={theme} /> : null}
|
||||
</>
|
||||
|
|
|
@ -10,6 +10,7 @@ import TextInput from '../../TextInput';
|
|||
|
||||
import { textParser } from '../utils';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import I18n from '../../../i18n';
|
||||
|
||||
import Chips from './Chips';
|
||||
import Items from './Items';
|
||||
|
@ -33,6 +34,10 @@ export const MultiSelect = React.memo(({
|
|||
loading,
|
||||
value: values,
|
||||
multiselect = false,
|
||||
onSearch,
|
||||
onClose,
|
||||
disabled,
|
||||
inputStyle,
|
||||
theme
|
||||
}) => {
|
||||
const [selected, select] = useState(values || []);
|
||||
|
@ -51,6 +56,12 @@ export const MultiSelect = React.memo(({
|
|||
setOpen(showContent);
|
||||
}, [showContent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (values && values.length && !multiselect) {
|
||||
setCurrentValue(values[0].text);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onShow = () => {
|
||||
Animated.timing(
|
||||
animatedValue,
|
||||
|
@ -63,6 +74,7 @@ export const MultiSelect = React.memo(({
|
|||
};
|
||||
|
||||
const onHide = () => {
|
||||
onClose();
|
||||
Animated.timing(
|
||||
animatedValue,
|
||||
{
|
||||
|
@ -73,7 +85,7 @@ export const MultiSelect = React.memo(({
|
|||
};
|
||||
|
||||
const onSelect = (item) => {
|
||||
const { value } = item;
|
||||
const { value, text: { text } } = item;
|
||||
if (multiselect) {
|
||||
let newSelect = [];
|
||||
if (!selected.includes(value)) {
|
||||
|
@ -85,20 +97,20 @@ export const MultiSelect = React.memo(({
|
|||
onChange({ value: newSelect });
|
||||
} else {
|
||||
onChange({ value });
|
||||
setCurrentValue(value);
|
||||
setOpen(false);
|
||||
setCurrentValue(text);
|
||||
onHide();
|
||||
}
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
const items = options.filter(option => textParser([option.text]).toLowerCase().includes(search.toLowerCase()));
|
||||
const items = onSearch ? options : options.filter(option => textParser([option.text]).toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
return (
|
||||
<View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<TextInput
|
||||
onChangeText={onSearchChange}
|
||||
placeholder={placeholder.text}
|
||||
onChangeText={onSearch || onSearchChange}
|
||||
placeholder={I18n.t('Search')}
|
||||
theme={theme}
|
||||
/>
|
||||
<Items items={items} selected={selected} onSelect={onSelect} theme={theme} />
|
||||
|
@ -124,19 +136,24 @@ export const MultiSelect = React.memo(({
|
|||
open={onShow}
|
||||
theme={theme}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
inputStyle={inputStyle}
|
||||
>
|
||||
<Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{currentValue}</Text>
|
||||
<Text style={[styles.pickerText, { color: currentValue ? themes[theme].titleText : themes[theme].auxiliaryText }]}>{currentValue || placeholder.text}</Text>
|
||||
</Input>
|
||||
);
|
||||
|
||||
if (context === BLOCK_CONTEXT.FORM) {
|
||||
const items = options.filter(option => selected.includes(option.value));
|
||||
button = (
|
||||
<Input
|
||||
open={onShow}
|
||||
theme={theme}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
inputStyle={inputStyle}
|
||||
>
|
||||
<Chips items={options.filter(option => selected.includes(option.value))} onSelect={onSelect} theme={theme} />
|
||||
{items.length ? <Chips items={items} onSelect={onSelect} theme={theme} /> : <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder.text}</Text>}
|
||||
</Input>
|
||||
);
|
||||
}
|
||||
|
@ -172,6 +189,13 @@ MultiSelect.propTypes = {
|
|||
context: PropTypes.number,
|
||||
loading: PropTypes.bool,
|
||||
multiselect: PropTypes.bool,
|
||||
onSearch: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
inputStyle: PropTypes.object,
|
||||
value: PropTypes.array,
|
||||
disabled: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
MultiSelect.defaultProps = {
|
||||
onClose: () => {}
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
pickerText: {
|
||||
...sharedStyles.textRegular,
|
||||
paddingLeft: 6,
|
||||
fontSize: 16
|
||||
},
|
||||
item: {
|
||||
|
@ -40,7 +41,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
input: {
|
||||
minHeight: 48,
|
||||
padding: 8,
|
||||
paddingHorizontal: 8,
|
||||
paddingBottom: 0,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderRadius: 2,
|
||||
|
@ -58,6 +59,7 @@ export default StyleSheet.create({
|
|||
height: 226
|
||||
},
|
||||
chips: {
|
||||
paddingTop: 8,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginRight: 50
|
||||
|
@ -82,5 +84,11 @@ export default StyleSheet.create({
|
|||
borderRadius: 2,
|
||||
width: 20,
|
||||
height: 20
|
||||
},
|
||||
itemImage: {
|
||||
marginRight: 8,
|
||||
borderRadius: 2,
|
||||
width: 24,
|
||||
height: 24
|
||||
}
|
||||
});
|
||||
|
|
|
@ -87,6 +87,7 @@ export default {
|
|||
alert: 'alert',
|
||||
alerts: 'alerts',
|
||||
All_users_in_the_channel_can_write_new_messages: 'All users in the channel can write new messages',
|
||||
A_meaningful_name_for_the_discussion_room: 'A meaningful name for the discussion room',
|
||||
All: 'All',
|
||||
All_Messages: 'All Messages',
|
||||
Allow_Reactions: 'Allow Reactions',
|
||||
|
@ -154,6 +155,7 @@ export default {
|
|||
Whats_the_password_for_your_certificate: 'What\'s the password for your certificate?',
|
||||
Create_account: 'Create an account',
|
||||
Create_Channel: 'Create Channel',
|
||||
Create_Discussion: 'Create Discussion',
|
||||
Created_snippet: 'Created a snippet',
|
||||
Create_a_new_workspace: 'Create a new workspace',
|
||||
Create: 'Create',
|
||||
|
@ -173,6 +175,8 @@ export default {
|
|||
Direct_Messages: 'Direct Messages',
|
||||
Disable_notifications: 'Disable notifications',
|
||||
Discussions: 'Discussions',
|
||||
Discussion_Desc: 'Help keeping an overview about what\'s going on! By creating a discussion, a sub-channel of the one you selected is created and both are linked.',
|
||||
Discussion_name: 'Discussion name',
|
||||
Dont_Have_An_Account: 'Don\'t you have an account?',
|
||||
Do_you_have_an_account: 'Do you have an account?',
|
||||
Do_you_have_a_certificate: 'Do you have a certificate?',
|
||||
|
@ -323,6 +327,7 @@ export default {
|
|||
OR: 'OR',
|
||||
Overwrites_the_server_configuration_and_use_room_config: 'Overwrites the server configuration and use room config',
|
||||
Password: 'Password',
|
||||
Parent_channel_or_group: 'Parent channel or group',
|
||||
Permalink_copied_to_clipboard: 'Permalink copied to clipboard!',
|
||||
Pin: 'Pin',
|
||||
Pinned_Messages: 'Pinned Messages',
|
||||
|
@ -400,6 +405,7 @@ export default {
|
|||
Select_Avatar: 'Select Avatar',
|
||||
Select_Server: 'Select Server',
|
||||
Select_Users: 'Select Users',
|
||||
Select_a_Channel: 'Select a Channel',
|
||||
Send: 'Send',
|
||||
Send_audio_message: 'Send audio message',
|
||||
Send_crash_report: 'Send crash report',
|
||||
|
@ -482,6 +488,7 @@ export default {
|
|||
Username: 'Username',
|
||||
Username_or_email: 'Username or email',
|
||||
Uses_server_configuration: 'Uses server configuration',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Usually, a discussion starts with a question, like "How do I upload a picture?"',
|
||||
Validating: 'Validating',
|
||||
Verify_email_title: 'Registration Succeeded!',
|
||||
Verify_email_desc: 'We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.',
|
||||
|
@ -508,6 +515,7 @@ export default {
|
|||
Logged_out_by_server: 'You\'ve been logged out by the server. Please log in again.',
|
||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
||||
Your_certificate: 'Your Certificate',
|
||||
Your_message: 'Your message',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Your invite link will expire on {{date}} or after {{usesLeft}} uses.',
|
||||
Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.',
|
||||
|
|
|
@ -93,6 +93,7 @@ export default {
|
|||
alert: 'alerta',
|
||||
alerts: 'alertas',
|
||||
All_users_in_the_channel_can_write_new_messages: 'Todos usuários no canal podem enviar mensagens novas',
|
||||
A_meaningful_name_for_the_discussion_room: 'Um nome significativo para o canal de discussão',
|
||||
All: 'Todos',
|
||||
Allow_Reactions: 'Permitir reagir',
|
||||
Alphabetical: 'Alfabético',
|
||||
|
@ -152,6 +153,7 @@ export default {
|
|||
Permalink: 'Link-Permanente',
|
||||
Create_account: 'Criar conta',
|
||||
Create_Channel: 'Criar Canal',
|
||||
Create_Discussion: 'Criar Discussão',
|
||||
Created_snippet: 'Criou um snippet',
|
||||
Create_a_new_workspace: 'Criar nova área de trabalho',
|
||||
Create: 'Criar',
|
||||
|
@ -169,6 +171,8 @@ export default {
|
|||
Description: 'Descrição',
|
||||
Disable_notifications: 'Desabilitar notificações',
|
||||
Discussions: 'Discussões',
|
||||
Discussion_Desc: 'Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, um sub-canal do que você selecionou é criado e os dois são vinculados.',
|
||||
Discussion_name: 'Nome da discussão',
|
||||
Dont_Have_An_Account: 'Não tem uma conta?',
|
||||
Do_you_have_an_account: 'Você tem uma conta?',
|
||||
Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?',
|
||||
|
@ -297,6 +301,7 @@ export default {
|
|||
OR: 'OU',
|
||||
Overwrites_the_server_configuration_and_use_room_config: 'Substituir a configuração do servidor e usar a configuração da sala',
|
||||
Password: 'Senha',
|
||||
Parent_channel_or_group: 'Canal ou grupo pai',
|
||||
Permalink_copied_to_clipboard: 'Link-permanente copiado para a área de transferência!',
|
||||
Pin: 'Fixar',
|
||||
Pinned_Messages: 'Mensagens Fixadas',
|
||||
|
@ -365,6 +370,7 @@ export default {
|
|||
Select_Avatar: 'Selecionar Avatar',
|
||||
Select_Server: 'Selecionar Servidor',
|
||||
Select_Users: 'Selecionar Usuários',
|
||||
Select_a_Channel: 'Selecione um canal',
|
||||
Send: 'Enviar',
|
||||
Send_audio_message: 'Enviar mensagem de áudio',
|
||||
Send_message: 'Enviar mensagem',
|
||||
|
@ -435,6 +441,7 @@ export default {
|
|||
Username: 'Usuário',
|
||||
Username_or_email: 'Usuário ou email',
|
||||
Uses_server_configuration: 'Usar configuração do servidor',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Normalmente, uma discussão começa com uma pergunta como: Como faço para enviar uma foto?',
|
||||
Verify_email_title: 'Registrado com sucesso!',
|
||||
Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.',
|
||||
Video_call: 'Chamada de vídeo',
|
||||
|
@ -449,6 +456,7 @@ export default {
|
|||
You_are_in_preview_mode: 'Está é uma prévia do canal',
|
||||
You_are_offline: 'Você está offline',
|
||||
You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`',
|
||||
Your_message: 'Sua mensagem',
|
||||
You_colon: 'Você: ',
|
||||
you_were_mentioned: 'você foi mencionado',
|
||||
You_were_removed_from_channel: 'Você foi removido de {{channel}}',
|
||||
|
|
11
app/index.js
11
app/index.js
|
@ -289,11 +289,21 @@ const ModalBlockStack = createStackNavigator({
|
|||
cardStyle
|
||||
});
|
||||
|
||||
const CreateDiscussionStack = createStackNavigator({
|
||||
CreateDiscussionView: {
|
||||
getScreen: () => require('./views/CreateDiscussionView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const InsideStackModal = createStackNavigator({
|
||||
Main: ChatsDrawer,
|
||||
NewMessageStack,
|
||||
AttachmentStack,
|
||||
ModalBlockStack,
|
||||
CreateDiscussionStack,
|
||||
JitsiMeetView: {
|
||||
getScreen: () => require('./views/JitsiMeetView').default
|
||||
}
|
||||
|
@ -438,6 +448,7 @@ const ModalSwitch = createSwitchNavigator({
|
|||
RoomActionsStack,
|
||||
SettingsStack,
|
||||
ModalBlockStack,
|
||||
CreateDiscussionStack,
|
||||
AuthLoading: () => null
|
||||
},
|
||||
{
|
||||
|
|
|
@ -605,6 +605,16 @@ const RocketChat = {
|
|||
// RC 0.59.0
|
||||
return this.sdk.post('im.create', { username });
|
||||
},
|
||||
|
||||
createDiscussion({
|
||||
prid, pmid, t_name, reply, users
|
||||
}) {
|
||||
// RC 1.0.0
|
||||
return this.sdk.post('rooms.createDiscussion', {
|
||||
prid, pmid, t_name, reply, users
|
||||
});
|
||||
},
|
||||
|
||||
joinRoom(roomId, type) {
|
||||
// TODO: join code
|
||||
// RC 0.48.0
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { CREATE_DISCUSSION } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
isFetching: false,
|
||||
failure: false,
|
||||
result: {},
|
||||
error: {}
|
||||
};
|
||||
|
||||
export default function(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case CREATE_DISCUSSION.REQUEST:
|
||||
return {
|
||||
...state,
|
||||
isFetching: true,
|
||||
failure: false,
|
||||
error: {}
|
||||
};
|
||||
case CREATE_DISCUSSION.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
isFetching: false,
|
||||
failure: false,
|
||||
result: action.data
|
||||
};
|
||||
case CREATE_DISCUSSION.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
isFetching: false,
|
||||
failure: true,
|
||||
error: action.err
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import customEmojis from './customEmojis';
|
|||
import activeUsers from './activeUsers';
|
||||
import usersTyping from './usersTyping';
|
||||
import inviteLinks from './inviteLinks';
|
||||
import createDiscussion from './createDiscussion';
|
||||
|
||||
export default combineReducers({
|
||||
settings,
|
||||
|
@ -34,5 +35,6 @@ export default combineReducers({
|
|||
customEmojis,
|
||||
activeUsers,
|
||||
usersTyping,
|
||||
inviteLinks
|
||||
inviteLinks,
|
||||
createDiscussion
|
||||
});
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import {
|
||||
select, put, call, take, takeLatest
|
||||
} from 'redux-saga/effects';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
|
||||
import { CREATE_DISCUSSION, LOGIN } from '../actions/actionsTypes';
|
||||
import { createDiscussionSuccess, createDiscussionFailure } from '../actions/createDiscussion';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/database';
|
||||
|
||||
const create = function* create(data) {
|
||||
return yield RocketChat.createDiscussion(data);
|
||||
};
|
||||
|
||||
const handleRequest = function* handleRequest({ data }) {
|
||||
try {
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
if (!auth) {
|
||||
yield take(LOGIN.SUCCESS);
|
||||
}
|
||||
const result = yield call(create, data);
|
||||
|
||||
if (result.success) {
|
||||
const { discussion: sub } = result;
|
||||
|
||||
try {
|
||||
const db = database.active;
|
||||
const subCollection = db.collections.get('subscriptions');
|
||||
yield db.action(async() => {
|
||||
await subCollection.create((s) => {
|
||||
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema);
|
||||
Object.assign(s, sub);
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
yield put(createDiscussionSuccess(sub));
|
||||
} else {
|
||||
yield put(createDiscussionFailure(result));
|
||||
}
|
||||
} catch (err) {
|
||||
yield put(createDiscussionFailure(err));
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(CREATE_DISCUSSION.REQUEST, handleRequest);
|
||||
};
|
||||
|
||||
export default root;
|
|
@ -9,6 +9,7 @@ import init from './init';
|
|||
import state from './state';
|
||||
import deepLinking from './deepLinking';
|
||||
import inviteLinks from './inviteLinks';
|
||||
import createDiscussion from './createDiscussion';
|
||||
|
||||
const root = function* root() {
|
||||
yield all([
|
||||
|
@ -21,7 +22,8 @@ const root = function* root() {
|
|||
selectServer(),
|
||||
state(),
|
||||
deepLinking(),
|
||||
inviteLinks()
|
||||
inviteLinks(),
|
||||
createDiscussion()
|
||||
]);
|
||||
};
|
||||
|
||||
|
|
|
@ -117,6 +117,11 @@ export const initTabletNav = (setState) => {
|
|||
setState({ showModal: true });
|
||||
return null;
|
||||
}
|
||||
if (routeName === 'CreateDiscussionView') {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||
setState({ showModal: true });
|
||||
return null;
|
||||
}
|
||||
|
||||
if (routeName === 'RoomView') {
|
||||
const resetAction = StackActions.reset({
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
|
||||
`${ baseUrl }${ url }?format=png&size=${ uriSize }&${ avatarAuthURLFragment }`
|
||||
);
|
||||
|
||||
export const avatarURL = ({
|
||||
type, text, size, userId, token, avatar, baseUrl
|
||||
}) => {
|
||||
const room = type === 'd' ? text : `@${ text }`;
|
||||
|
||||
// Avoid requesting several sizes by having only two sizes on cache
|
||||
const uriSize = size === 100 ? 100 : 50;
|
||||
|
||||
let avatarAuthURLFragment = '';
|
||||
if (userId && token) {
|
||||
avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`;
|
||||
}
|
||||
|
||||
|
||||
let uri;
|
||||
if (avatar) {
|
||||
uri = avatar.includes('http') ? avatar : formatUrl(avatar, baseUrl, uriSize, avatarAuthURLFragment);
|
||||
} else {
|
||||
uri = formatUrl(`/avatar/${ room }`, baseUrl, uriSize, avatarAuthURLFragment);
|
||||
}
|
||||
|
||||
return uri;
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import debounce from '../../utils/debounce';
|
||||
import { avatarURL } from '../../utils/avatar';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import I18n from '../../i18n';
|
||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
const SelectChannel = ({
|
||||
server, token, userId, onChannelSelect, initial, theme
|
||||
}) => {
|
||||
const [channels, setChannels] = useState([]);
|
||||
|
||||
const getChannels = debounce(async(keyword = '') => {
|
||||
try {
|
||||
const res = await RocketChat.search({ text: keyword, filterUsers: false });
|
||||
setChannels(res);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const getAvatar = (text, type) => avatarURL({
|
||||
text, type, userId, token, baseUrl: server
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text style={styles.label}>{I18n.t('Parent_channel_or_group')}</Text>
|
||||
<MultiSelect
|
||||
theme={theme}
|
||||
inputStyle={styles.inputStyle}
|
||||
onChange={onChannelSelect}
|
||||
onSearch={getChannels}
|
||||
value={initial && [initial]}
|
||||
disabled={initial}
|
||||
options={channels.map(channel => ({
|
||||
value: channel.rid,
|
||||
text: { text: RocketChat.getRoomTitle(channel) },
|
||||
imageUrl: getAvatar(RocketChat.getRoomAvatar(channel), channel.t)
|
||||
}))}
|
||||
onClose={() => setChannels([])}
|
||||
placeholder={{ text: `${ I18n.t('Select_a_Channel') }...` }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
SelectChannel.propTypes = {
|
||||
server: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
userId: PropTypes.string,
|
||||
initial: PropTypes.object,
|
||||
onChannelSelect: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default SelectChannel;
|
|
@ -0,0 +1,62 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||
|
||||
import debounce from '../../utils/debounce';
|
||||
import { avatarURL } from '../../utils/avatar';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import I18n from '../../i18n';
|
||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
const SelectUsers = ({
|
||||
server, token, userId, selected, onUserSelect, theme
|
||||
}) => {
|
||||
const [users, setUsers] = useState([]);
|
||||
|
||||
const getUsers = debounce(async(keyword = '') => {
|
||||
try {
|
||||
const res = await RocketChat.search({ text: keyword, filterRooms: false });
|
||||
setUsers([...users.filter(u => selected.includes(u.name)), ...res.filter(r => !users.find(u => u.name === r.name))]);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const getAvatar = text => avatarURL({
|
||||
text, type: 'd', userId, token, baseUrl: server
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text style={styles.label}>{I18n.t('Invite_users')}</Text>
|
||||
<MultiSelect
|
||||
theme={theme}
|
||||
inputStyle={styles.inputStyle}
|
||||
onSearch={getUsers}
|
||||
onChange={onUserSelect}
|
||||
options={users.map(user => ({
|
||||
value: user.name,
|
||||
text: { text: RocketChat.getRoomTitle(user) },
|
||||
imageUrl: getAvatar(RocketChat.getRoomAvatar(user))
|
||||
}))}
|
||||
onClose={() => setUsers(users.filter(u => selected.includes(u.name)))}
|
||||
placeholder={{ text: `${ I18n.t('Select_Users') }...` }}
|
||||
context={BLOCK_CONTEXT.FORM}
|
||||
multiselect
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
SelectUsers.propTypes = {
|
||||
server: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
userId: PropTypes.string,
|
||||
selected: PropTypes.array,
|
||||
onUserSelect: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default SelectUsers;
|
|
@ -0,0 +1,195 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScrollView, Text } from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import Loading from '../../containers/Loading';
|
||||
import KeyboardView from '../../presentation/KeyboardView';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import I18n from '../../i18n';
|
||||
import { CustomHeaderButtons, Item, CloseModalButton } from '../../containers/HeaderButton';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import TextInput from '../../containers/TextInput';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import Navigation from '../../lib/Navigation';
|
||||
import { createDiscussionRequest } from '../../actions/createDiscussion';
|
||||
import { showErrorAlert } from '../../utils/info';
|
||||
|
||||
import SelectChannel from './SelectChannel';
|
||||
import SelectUsers from './SelectUsers';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
class CreateChannelView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const submit = navigation.getParam('submit', () => {});
|
||||
const showSubmit = navigation.getParam('showSubmit', navigation.getParam('message'));
|
||||
return {
|
||||
...themedHeader(screenProps.theme),
|
||||
title: I18n.t('Create_Discussion'),
|
||||
headerRight: (
|
||||
showSubmit
|
||||
? (
|
||||
<CustomHeaderButtons>
|
||||
<Item title={I18n.t('Create')} onPress={submit} testID='create-discussion-submit' />
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
: null
|
||||
),
|
||||
headerLeft: <CloseModalButton navigation={navigation} />
|
||||
};
|
||||
}
|
||||
|
||||
propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
server: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
create: PropTypes.func,
|
||||
loading: PropTypes.bool,
|
||||
result: PropTypes.object,
|
||||
failure: PropTypes.bool,
|
||||
error: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { navigation } = props;
|
||||
navigation.setParams({ submit: this.submit });
|
||||
this.channel = navigation.getParam('channel');
|
||||
const message = navigation.getParam('message', {});
|
||||
this.state = {
|
||||
channel: this.channel,
|
||||
message,
|
||||
name: message.msg || '',
|
||||
users: [],
|
||||
reply: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
loading, failure, error, result, navigation
|
||||
} = this.props;
|
||||
|
||||
if (!isEqual(this.state, prevState)) {
|
||||
navigation.setParams({ showSubmit: this.valid() });
|
||||
}
|
||||
|
||||
if (!loading && loading !== prevProps.loading) {
|
||||
setTimeout(() => {
|
||||
if (failure) {
|
||||
const msg = error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
|
||||
showErrorAlert(msg);
|
||||
} else {
|
||||
const { rid, t, prid } = result;
|
||||
if (this.channel) {
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
Navigation.navigate('RoomView', {
|
||||
rid, name: RocketChat.getRoomTitle(result), t, prid
|
||||
});
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
const {
|
||||
name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users
|
||||
} = this.state;
|
||||
const { create } = this.props;
|
||||
|
||||
// create discussion
|
||||
create({
|
||||
prid: prid || rid, pmid, t_name, reply, users
|
||||
});
|
||||
};
|
||||
|
||||
valid = () => {
|
||||
const {
|
||||
channel, name
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
channel
|
||||
&& channel.rid
|
||||
&& channel.rid.trim().length
|
||||
&& name.trim().length
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, users } = this.state;
|
||||
const {
|
||||
server, user, loading, theme
|
||||
} = this.props;
|
||||
return (
|
||||
<KeyboardView
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
contentContainerStyle={styles.container}
|
||||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<SafeAreaView testID='create-discussion-view' style={styles.container} forceInset={{ vertical: 'never' }}>
|
||||
<ScrollView {...scrollPersistTaps}>
|
||||
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Discussion_Desc')}</Text>
|
||||
<SelectChannel
|
||||
server={server}
|
||||
userId={user.id}
|
||||
token={user.token}
|
||||
initial={this.channel && { text: RocketChat.getRoomTitle(this.channel) }}
|
||||
onChannelSelect={({ value }) => this.setState({ channel: { rid: value } })}
|
||||
theme={theme}
|
||||
/>
|
||||
<TextInput
|
||||
label={I18n.t('Discussion_name')}
|
||||
placeholder={I18n.t('A_meaningful_name_for_the_discussion_room')}
|
||||
containerStyle={styles.inputStyle}
|
||||
defaultValue={name}
|
||||
onChangeText={text => this.setState({ name: text })}
|
||||
/>
|
||||
<SelectUsers
|
||||
server={server}
|
||||
userId={user.id}
|
||||
token={user.token}
|
||||
selected={users}
|
||||
onUserSelect={({ value }) => this.setState({ users: value })}
|
||||
theme={theme}
|
||||
/>
|
||||
<TextInput
|
||||
multiline
|
||||
label={I18n.t('Your_message')}
|
||||
inputStyle={styles.multiline}
|
||||
theme={theme}
|
||||
placeholder={I18n.t('Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture')}
|
||||
onChangeText={text => this.setState({ reply: text })}
|
||||
/>
|
||||
<Loading visible={loading} />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: getUserSelector(state),
|
||||
server: state.server.server,
|
||||
error: state.createDiscussion.error,
|
||||
failure: state.createDiscussion.failure,
|
||||
loading: state.createDiscussion.isFetching,
|
||||
result: state.createDiscussion.result
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
create: data => dispatch(createDiscussionRequest(data))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(CreateChannelView));
|
|
@ -0,0 +1,24 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../Styles';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 8
|
||||
},
|
||||
multiline: {
|
||||
height: 130
|
||||
},
|
||||
label: {
|
||||
marginBottom: 10,
|
||||
fontSize: 14,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
inputStyle: {
|
||||
marginBottom: 16
|
||||
},
|
||||
description: {
|
||||
paddingBottom: 16
|
||||
}
|
||||
});
|
|
@ -24,6 +24,7 @@ import { themes } from '../constants/colors';
|
|||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { getUserSelector } from '../selectors/login';
|
||||
import Navigation from '../lib/Navigation';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeAreaView: {
|
||||
|
@ -33,7 +34,10 @@ const styles = StyleSheet.create({
|
|||
marginLeft: 60
|
||||
},
|
||||
createChannelButton: {
|
||||
marginVertical: 25
|
||||
marginTop: 25
|
||||
},
|
||||
createDiscussionButton: {
|
||||
marginBottom: 25
|
||||
},
|
||||
createChannelContainer: {
|
||||
height: 46,
|
||||
|
@ -42,7 +46,7 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
createChannelIcon: {
|
||||
marginLeft: 18,
|
||||
marginRight: 15
|
||||
marginRight: 16
|
||||
},
|
||||
createChannelText: {
|
||||
fontSize: 17,
|
||||
|
@ -142,6 +146,10 @@ class NewMessageView extends React.Component {
|
|||
navigation.navigate('SelectedUsersViewCreateChannel', { nextActionID: 'CREATE_CHANNEL', title: I18n.t('Select_Users') });
|
||||
}
|
||||
|
||||
createDiscussion = () => {
|
||||
Navigation.navigate('CreateDiscussionView');
|
||||
}
|
||||
|
||||
renderHeader = () => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
|
@ -154,10 +162,21 @@ class NewMessageView extends React.Component {
|
|||
theme={theme}
|
||||
>
|
||||
<View style={[sharedStyles.separatorVertical, styles.createChannelContainer, { borderColor: themes[theme].separatorColor }]}>
|
||||
<CustomIcon style={[styles.createChannelIcon, { color: themes[theme].tintColor }]} size={24} name='plus' />
|
||||
<CustomIcon style={[styles.createChannelIcon, { color: themes[theme].tintColor }]} size={24} name='hashtag' />
|
||||
<Text style={[styles.createChannelText, { color: themes[theme].tintColor }]}>{I18n.t('Create_Channel')}</Text>
|
||||
</View>
|
||||
</Touch>
|
||||
<Touch
|
||||
onPress={this.createDiscussion}
|
||||
style={[styles.createDiscussionButton, { backgroundColor: themes[theme].backgroundColor }]}
|
||||
testID='new-message-view-create-discussion'
|
||||
theme={theme}
|
||||
>
|
||||
<View style={[sharedStyles.separatorBottom, styles.createChannelContainer, { borderColor: themes[theme].separatorColor }]}>
|
||||
<CustomIcon style={[styles.createChannelIcon, { color: themes[theme].tintColor }]} size={24} name='chat' />
|
||||
<Text style={[styles.createChannelText, { color: themes[theme].tintColor }]}>{I18n.t('Create_Discussion')}</Text>
|
||||
</View>
|
||||
</Touch>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue