[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 | ✅ |
|
| Jitsi Integration | ✅ |
|
||||||
| Federation (Directory) | ✅ |
|
| Federation (Directory) | ✅ |
|
||||||
| Discussions | ❌ |
|
| Discussions | ✅ |
|
||||||
| Omnichannel | ❌ |
|
| Omnichannel | ❌ |
|
||||||
| Threads | ✅ |
|
| Threads | ✅ |
|
||||||
| Record Audio | ✅ |
|
| 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 APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
|
||||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
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 SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER', 'REMOVE_USER', 'RESET', 'SET_LOADING']);
|
||||||
export const SERVER = createRequestTypes('SERVER', [
|
export const SERVER = createRequestTypes('SERVER', [
|
||||||
...defaultTypes,
|
...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 FastImage from 'react-native-fast-image';
|
||||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||||
import Touch from '../utils/touch';
|
import Touch from '../utils/touch';
|
||||||
|
import { avatarURL } from '../utils/avatar';
|
||||||
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
|
|
||||||
`${ baseUrl }${ url }?format=png&size=${ uriSize }&${ avatarAuthURLFragment }`
|
|
||||||
);
|
|
||||||
|
|
||||||
const Avatar = React.memo(({
|
const Avatar = React.memo(({
|
||||||
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress, theme
|
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress, theme
|
||||||
|
@ -22,24 +19,9 @@ const Avatar = React.memo(({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const room = type === 'd' ? text : `@${ text }`;
|
const uri = avatarURL({
|
||||||
|
type, text, size, userId, token, avatar, baseUrl
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let image = (
|
let image = (
|
||||||
<FastImage
|
<FastImage
|
||||||
|
|
|
@ -63,6 +63,10 @@ class MessageActions extends React.Component {
|
||||||
this.EDIT_INDEX = this.options.length - 1;
|
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
|
// Mark as unread
|
||||||
if (message.u && message.u._id !== user.id) {
|
if (message.u && message.u._id !== user.id) {
|
||||||
this.options.push(I18n.t('Mark_unread'));
|
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) => {
|
handleActionPress = (actionIndex) => {
|
||||||
if (actionIndex) {
|
if (actionIndex) {
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
|
@ -413,6 +422,9 @@ class MessageActions extends React.Component {
|
||||||
case this.READ_RECEIPT_INDEX:
|
case this.READ_RECEIPT_INDEX:
|
||||||
this.handleReadReceipt();
|
this.handleReadReceipt();
|
||||||
break;
|
break;
|
||||||
|
case this.CREATE_DISCUSSION_INDEX:
|
||||||
|
this.handleCreateDiscussion();
|
||||||
|
break;
|
||||||
case this.TOGGLE_TRANSLATION_INDEX:
|
case this.TOGGLE_TRANSLATION_INDEX:
|
||||||
this.handleToggleTranslation();
|
this.handleToggleTranslation();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { CancelEditingButton, FileButton } from './buttons';
|
import { CancelEditingButton, ActionsButton } from './buttons';
|
||||||
|
|
||||||
const LeftButtons = React.memo(({
|
const LeftButtons = React.memo(({
|
||||||
theme, showFileActions, editing, editCancel
|
theme, showMessageBoxActions, editing, editCancel
|
||||||
}) => {
|
}) => {
|
||||||
if (editing) {
|
if (editing) {
|
||||||
return <CancelEditingButton onPress={editCancel} theme={theme} />;
|
return <CancelEditingButton onPress={editCancel} theme={theme} />;
|
||||||
}
|
}
|
||||||
return <FileButton onPress={showFileActions} theme={theme} />;
|
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
LeftButtons.propTypes = {
|
LeftButtons.propTypes = {
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
showFileActions: PropTypes.func.isRequired,
|
showMessageBoxActions: PropTypes.func.isRequired,
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
editCancel: PropTypes.func.isRequired
|
editCancel: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { SendButton, AudioButton, FileButton } from './buttons';
|
import { SendButton, AudioButton, ActionsButton } from './buttons';
|
||||||
|
|
||||||
const RightButtons = React.memo(({
|
const RightButtons = React.memo(({
|
||||||
theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled, showFileActions
|
theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled, showMessageBoxActions
|
||||||
}) => {
|
}) => {
|
||||||
if (showSend) {
|
if (showSend) {
|
||||||
return <SendButton onPress={submit} theme={theme} />;
|
return <SendButton onPress={submit} theme={theme} />;
|
||||||
|
@ -13,11 +13,11 @@ const RightButtons = React.memo(({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AudioButton onPress={recordAudioMessage} theme={theme} />
|
<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 = {
|
RightButtons.propTypes = {
|
||||||
|
@ -26,7 +26,7 @@ RightButtons.propTypes = {
|
||||||
submit: PropTypes.func.isRequired,
|
submit: PropTypes.func.isRequired,
|
||||||
recordAudioMessage: PropTypes.func.isRequired,
|
recordAudioMessage: PropTypes.func.isRequired,
|
||||||
recordAudioMessageEnabled: PropTypes.bool,
|
recordAudioMessageEnabled: PropTypes.bool,
|
||||||
showFileActions: PropTypes.func.isRequired
|
showMessageBoxActions: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RightButtons;
|
export default RightButtons;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import BaseButton from './BaseButton';
|
import BaseButton from './BaseButton';
|
||||||
|
|
||||||
const FileButton = React.memo(({ theme, onPress }) => (
|
const ActionsButton = React.memo(({ theme, onPress }) => (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
testID='messagebox-actions'
|
testID='messagebox-actions'
|
||||||
|
@ -13,9 +13,9 @@ const FileButton = React.memo(({ theme, onPress }) => (
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
FileButton.propTypes = {
|
ActionsButton.propTypes = {
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
onPress: PropTypes.func.isRequired
|
onPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FileButton;
|
export default ActionsButton;
|
|
@ -2,12 +2,12 @@ import CancelEditingButton from './CancelEditingButton';
|
||||||
import ToggleEmojiButton from './ToggleEmojiButton';
|
import ToggleEmojiButton from './ToggleEmojiButton';
|
||||||
import SendButton from './SendButton';
|
import SendButton from './SendButton';
|
||||||
import AudioButton from './AudioButton';
|
import AudioButton from './AudioButton';
|
||||||
import FileButton from './FileButton';
|
import ActionsButton from './ActionsButton';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CancelEditingButton,
|
CancelEditingButton,
|
||||||
ToggleEmojiButton,
|
ToggleEmojiButton,
|
||||||
SendButton,
|
SendButton,
|
||||||
AudioButton,
|
AudioButton,
|
||||||
FileButton
|
ActionsButton
|
||||||
};
|
};
|
||||||
|
|
|
@ -45,6 +45,7 @@ import {
|
||||||
import CommandsPreview from './CommandsPreview';
|
import CommandsPreview from './CommandsPreview';
|
||||||
import { Review } from '../../utils/review';
|
import { Review } from '../../utils/review';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
import Navigation from '../../lib/Navigation';
|
||||||
|
|
||||||
const imagePickerConfig = {
|
const imagePickerConfig = {
|
||||||
cropping: true,
|
cropping: true,
|
||||||
|
@ -65,6 +66,7 @@ const FILE_PHOTO_INDEX = 1;
|
||||||
const FILE_VIDEO_INDEX = 2;
|
const FILE_VIDEO_INDEX = 2;
|
||||||
const FILE_LIBRARY_INDEX = 3;
|
const FILE_LIBRARY_INDEX = 3;
|
||||||
const FILE_DOCUMENT_INDEX = 4;
|
const FILE_DOCUMENT_INDEX = 4;
|
||||||
|
const CREATE_DISCUSSION_INDEX = 5;
|
||||||
|
|
||||||
class MessageBox extends Component {
|
class MessageBox extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -113,12 +115,13 @@ class MessageBox extends Component {
|
||||||
};
|
};
|
||||||
this.text = '';
|
this.text = '';
|
||||||
this.focused = false;
|
this.focused = false;
|
||||||
this.fileOptions = [
|
this.messageBoxActions = [
|
||||||
I18n.t('Cancel'),
|
I18n.t('Cancel'),
|
||||||
I18n.t('Take_a_photo'),
|
I18n.t('Take_a_photo'),
|
||||||
I18n.t('Take_a_video'),
|
I18n.t('Take_a_video'),
|
||||||
I18n.t('Choose_from_library'),
|
I18n.t('Choose_from_library'),
|
||||||
I18n.t('Choose_file')
|
I18n.t('Choose_file'),
|
||||||
|
I18n.t('Create_Discussion')
|
||||||
];
|
];
|
||||||
const libPickerLabels = {
|
const libPickerLabels = {
|
||||||
cropperChooseText: I18n.t('Choose'),
|
cropperChooseText: I18n.t('Choose'),
|
||||||
|
@ -157,8 +160,8 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const room = await subsCollection.find(rid);
|
this.room = await subsCollection.find(rid);
|
||||||
msg = room.draftMessage;
|
msg = this.room.draftMessage;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Messagebox.didMount: Room not found');
|
console.log('Messagebox.didMount: Room not found');
|
||||||
}
|
}
|
||||||
|
@ -588,20 +591,24 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createDiscussion = () => {
|
||||||
|
Navigation.navigate('CreateDiscussionView', { channel: this.room });
|
||||||
|
}
|
||||||
|
|
||||||
showUploadModal = (file) => {
|
showUploadModal = (file) => {
|
||||||
this.setState({ file: { ...file, isVisible: true } });
|
this.setState({ file: { ...file, isVisible: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
showFileActions = () => {
|
showMessageBoxActions = () => {
|
||||||
ActionSheet.showActionSheetWithOptions({
|
ActionSheet.showActionSheetWithOptions({
|
||||||
options: this.fileOptions,
|
options: this.messageBoxActions,
|
||||||
cancelButtonIndex: FILE_CANCEL_INDEX
|
cancelButtonIndex: FILE_CANCEL_INDEX
|
||||||
}, (actionIndex) => {
|
}, (actionIndex) => {
|
||||||
this.handleFileActionPress(actionIndex);
|
this.handleMessageBoxActions(actionIndex);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFileActionPress = (actionIndex) => {
|
handleMessageBoxActions = (actionIndex) => {
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
case FILE_PHOTO_INDEX:
|
case FILE_PHOTO_INDEX:
|
||||||
this.takePhoto();
|
this.takePhoto();
|
||||||
|
@ -615,6 +622,9 @@ class MessageBox extends Component {
|
||||||
case FILE_DOCUMENT_INDEX:
|
case FILE_DOCUMENT_INDEX:
|
||||||
this.chooseFile();
|
this.chooseFile();
|
||||||
break;
|
break;
|
||||||
|
case CREATE_DISCUSSION_INDEX:
|
||||||
|
this.createDiscussion();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -783,7 +793,7 @@ class MessageBox extends Component {
|
||||||
} else if (handleCommandSubmit(event)) {
|
} else if (handleCommandSubmit(event)) {
|
||||||
this.submit();
|
this.submit();
|
||||||
} else if (handleCommandShowUpload(event)) {
|
} else if (handleCommandShowUpload(event)) {
|
||||||
this.showFileActions();
|
this.showMessageBoxActions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -828,7 +838,7 @@ class MessageBox extends Component {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
showEmojiKeyboard={showEmojiKeyboard}
|
showEmojiKeyboard={showEmojiKeyboard}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
showFileActions={this.showFileActions}
|
showMessageBoxActions={this.showMessageBoxActions}
|
||||||
editCancel={this.editCancel}
|
editCancel={this.editCancel}
|
||||||
openEmoji={this.openEmoji}
|
openEmoji={this.openEmoji}
|
||||||
closeEmoji={this.closeEmoji}
|
closeEmoji={this.closeEmoji}
|
||||||
|
@ -854,7 +864,7 @@ class MessageBox extends Component {
|
||||||
submit={this.submit}
|
submit={this.submit}
|
||||||
recordAudioMessage={this.recordAudioMessage}
|
recordAudioMessage={this.recordAudioMessage}
|
||||||
recordAudioMessageEnabled={Message_AudioRecorderEnabled}
|
recordAudioMessageEnabled={Message_AudioRecorderEnabled}
|
||||||
showFileActions={this.showFileActions}
|
showMessageBoxActions={this.showMessageBoxActions}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View, Image } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import { textParser } from '../utils';
|
import { textParser } from '../utils';
|
||||||
|
@ -19,7 +20,7 @@ const Chip = ({ item, onSelect, theme }) => (
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
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>
|
<Text numberOfLines={1} style={[styles.chipText, { color: themes[theme].titleText }]}>{textParser([item.text])}</Text>
|
||||||
<CustomIcon name='cross' size={16} color={themes[theme].auxiliaryText} />
|
<CustomIcon name='cross' size={16} color={themes[theme].auxiliaryText} />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -9,12 +9,13 @@ import ActivityIndicator from '../../ActivityIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const Input = ({
|
const Input = ({
|
||||||
children, open, theme, loading
|
children, open, theme, loading, inputStyle, disabled
|
||||||
}) => (
|
}) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => open(true)}
|
onPress={() => open(true)}
|
||||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<View style={[styles.input, { borderColor: themes[theme].separatorColor }]}>
|
<View style={[styles.input, { borderColor: themes[theme].separatorColor }]}>
|
||||||
{children}
|
{children}
|
||||||
|
@ -30,6 +31,8 @@ Input.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
open: PropTypes.func,
|
open: PropTypes.func,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
|
inputStyle: PropTypes.object,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
loading: PropTypes.bool
|
loading: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { Text, FlatList } from 'react-native';
|
import { Text, FlatList } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
import Separator from '../../Separator';
|
import Separator from '../../Separator';
|
||||||
import Check from '../../Check';
|
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>
|
<Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text>
|
||||||
{selected ? <Check theme={theme} /> : null}
|
{selected ? <Check theme={theme} /> : null}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import TextInput from '../../TextInput';
|
||||||
|
|
||||||
import { textParser } from '../utils';
|
import { textParser } from '../utils';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
|
import I18n from '../../../i18n';
|
||||||
|
|
||||||
import Chips from './Chips';
|
import Chips from './Chips';
|
||||||
import Items from './Items';
|
import Items from './Items';
|
||||||
|
@ -33,6 +34,10 @@ export const MultiSelect = React.memo(({
|
||||||
loading,
|
loading,
|
||||||
value: values,
|
value: values,
|
||||||
multiselect = false,
|
multiselect = false,
|
||||||
|
onSearch,
|
||||||
|
onClose,
|
||||||
|
disabled,
|
||||||
|
inputStyle,
|
||||||
theme
|
theme
|
||||||
}) => {
|
}) => {
|
||||||
const [selected, select] = useState(values || []);
|
const [selected, select] = useState(values || []);
|
||||||
|
@ -51,6 +56,12 @@ export const MultiSelect = React.memo(({
|
||||||
setOpen(showContent);
|
setOpen(showContent);
|
||||||
}, [showContent]);
|
}, [showContent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (values && values.length && !multiselect) {
|
||||||
|
setCurrentValue(values[0].text);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onShow = () => {
|
const onShow = () => {
|
||||||
Animated.timing(
|
Animated.timing(
|
||||||
animatedValue,
|
animatedValue,
|
||||||
|
@ -63,6 +74,7 @@ export const MultiSelect = React.memo(({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onHide = () => {
|
const onHide = () => {
|
||||||
|
onClose();
|
||||||
Animated.timing(
|
Animated.timing(
|
||||||
animatedValue,
|
animatedValue,
|
||||||
{
|
{
|
||||||
|
@ -73,7 +85,7 @@ export const MultiSelect = React.memo(({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (item) => {
|
const onSelect = (item) => {
|
||||||
const { value } = item;
|
const { value, text: { text } } = item;
|
||||||
if (multiselect) {
|
if (multiselect) {
|
||||||
let newSelect = [];
|
let newSelect = [];
|
||||||
if (!selected.includes(value)) {
|
if (!selected.includes(value)) {
|
||||||
|
@ -85,20 +97,20 @@ export const MultiSelect = React.memo(({
|
||||||
onChange({ value: newSelect });
|
onChange({ value: newSelect });
|
||||||
} else {
|
} else {
|
||||||
onChange({ value });
|
onChange({ value });
|
||||||
setCurrentValue(value);
|
setCurrentValue(text);
|
||||||
setOpen(false);
|
onHide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderContent = () => {
|
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 (
|
return (
|
||||||
<View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}>
|
<View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
|
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
<TextInput
|
<TextInput
|
||||||
onChangeText={onSearchChange}
|
onChangeText={onSearch || onSearchChange}
|
||||||
placeholder={placeholder.text}
|
placeholder={I18n.t('Search')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<Items items={items} selected={selected} onSelect={onSelect} theme={theme} />
|
<Items items={items} selected={selected} onSelect={onSelect} theme={theme} />
|
||||||
|
@ -124,19 +136,24 @@ export const MultiSelect = React.memo(({
|
||||||
open={onShow}
|
open={onShow}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
loading={loading}
|
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>
|
</Input>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context === BLOCK_CONTEXT.FORM) {
|
if (context === BLOCK_CONTEXT.FORM) {
|
||||||
|
const items = options.filter(option => selected.includes(option.value));
|
||||||
button = (
|
button = (
|
||||||
<Input
|
<Input
|
||||||
open={onShow}
|
open={onShow}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
loading={loading}
|
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>
|
</Input>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -172,6 +189,13 @@ MultiSelect.propTypes = {
|
||||||
context: PropTypes.number,
|
context: PropTypes.number,
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
multiselect: PropTypes.bool,
|
multiselect: PropTypes.bool,
|
||||||
|
onSearch: PropTypes.func,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
inputStyle: PropTypes.object,
|
||||||
value: PropTypes.array,
|
value: PropTypes.array,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
MultiSelect.defaultProps = {
|
||||||
|
onClose: () => {}
|
||||||
|
};
|
||||||
|
|
|
@ -30,6 +30,7 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
pickerText: {
|
pickerText: {
|
||||||
...sharedStyles.textRegular,
|
...sharedStyles.textRegular,
|
||||||
|
paddingLeft: 6,
|
||||||
fontSize: 16
|
fontSize: 16
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
|
@ -40,7 +41,7 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
minHeight: 48,
|
minHeight: 48,
|
||||||
padding: 8,
|
paddingHorizontal: 8,
|
||||||
paddingBottom: 0,
|
paddingBottom: 0,
|
||||||
borderWidth: StyleSheet.hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
|
@ -58,6 +59,7 @@ export default StyleSheet.create({
|
||||||
height: 226
|
height: 226
|
||||||
},
|
},
|
||||||
chips: {
|
chips: {
|
||||||
|
paddingTop: 8,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
marginRight: 50
|
marginRight: 50
|
||||||
|
@ -82,5 +84,11 @@ export default StyleSheet.create({
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20
|
height: 20
|
||||||
|
},
|
||||||
|
itemImage: {
|
||||||
|
marginRight: 8,
|
||||||
|
borderRadius: 2,
|
||||||
|
width: 24,
|
||||||
|
height: 24
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -87,6 +87,7 @@ export default {
|
||||||
alert: 'alert',
|
alert: 'alert',
|
||||||
alerts: 'alerts',
|
alerts: 'alerts',
|
||||||
All_users_in_the_channel_can_write_new_messages: 'All users in the channel can write new messages',
|
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: 'All',
|
||||||
All_Messages: 'All Messages',
|
All_Messages: 'All Messages',
|
||||||
Allow_Reactions: 'Allow Reactions',
|
Allow_Reactions: 'Allow Reactions',
|
||||||
|
@ -154,6 +155,7 @@ export default {
|
||||||
Whats_the_password_for_your_certificate: 'What\'s the password for your certificate?',
|
Whats_the_password_for_your_certificate: 'What\'s the password for your certificate?',
|
||||||
Create_account: 'Create an account',
|
Create_account: 'Create an account',
|
||||||
Create_Channel: 'Create Channel',
|
Create_Channel: 'Create Channel',
|
||||||
|
Create_Discussion: 'Create Discussion',
|
||||||
Created_snippet: 'Created a snippet',
|
Created_snippet: 'Created a snippet',
|
||||||
Create_a_new_workspace: 'Create a new workspace',
|
Create_a_new_workspace: 'Create a new workspace',
|
||||||
Create: 'Create',
|
Create: 'Create',
|
||||||
|
@ -173,6 +175,8 @@ export default {
|
||||||
Direct_Messages: 'Direct Messages',
|
Direct_Messages: 'Direct Messages',
|
||||||
Disable_notifications: 'Disable notifications',
|
Disable_notifications: 'Disable notifications',
|
||||||
Discussions: 'Discussions',
|
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?',
|
Dont_Have_An_Account: 'Don\'t you have an account?',
|
||||||
Do_you_have_an_account: 'Do you have an account?',
|
Do_you_have_an_account: 'Do you have an account?',
|
||||||
Do_you_have_a_certificate: 'Do you have a certificate?',
|
Do_you_have_a_certificate: 'Do you have a certificate?',
|
||||||
|
@ -323,6 +327,7 @@ export default {
|
||||||
OR: 'OR',
|
OR: 'OR',
|
||||||
Overwrites_the_server_configuration_and_use_room_config: 'Overwrites the server configuration and use room config',
|
Overwrites_the_server_configuration_and_use_room_config: 'Overwrites the server configuration and use room config',
|
||||||
Password: 'Password',
|
Password: 'Password',
|
||||||
|
Parent_channel_or_group: 'Parent channel or group',
|
||||||
Permalink_copied_to_clipboard: 'Permalink copied to clipboard!',
|
Permalink_copied_to_clipboard: 'Permalink copied to clipboard!',
|
||||||
Pin: 'Pin',
|
Pin: 'Pin',
|
||||||
Pinned_Messages: 'Pinned Messages',
|
Pinned_Messages: 'Pinned Messages',
|
||||||
|
@ -400,6 +405,7 @@ export default {
|
||||||
Select_Avatar: 'Select Avatar',
|
Select_Avatar: 'Select Avatar',
|
||||||
Select_Server: 'Select Server',
|
Select_Server: 'Select Server',
|
||||||
Select_Users: 'Select Users',
|
Select_Users: 'Select Users',
|
||||||
|
Select_a_Channel: 'Select a Channel',
|
||||||
Send: 'Send',
|
Send: 'Send',
|
||||||
Send_audio_message: 'Send audio message',
|
Send_audio_message: 'Send audio message',
|
||||||
Send_crash_report: 'Send crash report',
|
Send_crash_report: 'Send crash report',
|
||||||
|
@ -482,6 +488,7 @@ export default {
|
||||||
Username: 'Username',
|
Username: 'Username',
|
||||||
Username_or_email: 'Username or email',
|
Username_or_email: 'Username or email',
|
||||||
Uses_server_configuration: 'Uses server configuration',
|
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',
|
Validating: 'Validating',
|
||||||
Verify_email_title: 'Registration Succeeded!',
|
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.',
|
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.',
|
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.',
|
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_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_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__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}}.',
|
Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.',
|
||||||
|
|
|
@ -93,6 +93,7 @@ export default {
|
||||||
alert: 'alerta',
|
alert: 'alerta',
|
||||||
alerts: 'alertas',
|
alerts: 'alertas',
|
||||||
All_users_in_the_channel_can_write_new_messages: 'Todos usuários no canal podem enviar mensagens novas',
|
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',
|
All: 'Todos',
|
||||||
Allow_Reactions: 'Permitir reagir',
|
Allow_Reactions: 'Permitir reagir',
|
||||||
Alphabetical: 'Alfabético',
|
Alphabetical: 'Alfabético',
|
||||||
|
@ -152,6 +153,7 @@ export default {
|
||||||
Permalink: 'Link-Permanente',
|
Permalink: 'Link-Permanente',
|
||||||
Create_account: 'Criar conta',
|
Create_account: 'Criar conta',
|
||||||
Create_Channel: 'Criar Canal',
|
Create_Channel: 'Criar Canal',
|
||||||
|
Create_Discussion: 'Criar Discussão',
|
||||||
Created_snippet: 'Criou um snippet',
|
Created_snippet: 'Criou um snippet',
|
||||||
Create_a_new_workspace: 'Criar nova área de trabalho',
|
Create_a_new_workspace: 'Criar nova área de trabalho',
|
||||||
Create: 'Criar',
|
Create: 'Criar',
|
||||||
|
@ -169,6 +171,8 @@ export default {
|
||||||
Description: 'Descrição',
|
Description: 'Descrição',
|
||||||
Disable_notifications: 'Desabilitar notificações',
|
Disable_notifications: 'Desabilitar notificações',
|
||||||
Discussions: 'Discussõ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?',
|
Dont_Have_An_Account: 'Não tem uma conta?',
|
||||||
Do_you_have_an_account: 'Você 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?',
|
Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?',
|
||||||
|
@ -297,6 +301,7 @@ export default {
|
||||||
OR: 'OU',
|
OR: 'OU',
|
||||||
Overwrites_the_server_configuration_and_use_room_config: 'Substituir a configuração do servidor e usar a configuração da sala',
|
Overwrites_the_server_configuration_and_use_room_config: 'Substituir a configuração do servidor e usar a configuração da sala',
|
||||||
Password: 'Senha',
|
Password: 'Senha',
|
||||||
|
Parent_channel_or_group: 'Canal ou grupo pai',
|
||||||
Permalink_copied_to_clipboard: 'Link-permanente copiado para a área de transferência!',
|
Permalink_copied_to_clipboard: 'Link-permanente copiado para a área de transferência!',
|
||||||
Pin: 'Fixar',
|
Pin: 'Fixar',
|
||||||
Pinned_Messages: 'Mensagens Fixadas',
|
Pinned_Messages: 'Mensagens Fixadas',
|
||||||
|
@ -365,6 +370,7 @@ export default {
|
||||||
Select_Avatar: 'Selecionar Avatar',
|
Select_Avatar: 'Selecionar Avatar',
|
||||||
Select_Server: 'Selecionar Servidor',
|
Select_Server: 'Selecionar Servidor',
|
||||||
Select_Users: 'Selecionar Usuários',
|
Select_Users: 'Selecionar Usuários',
|
||||||
|
Select_a_Channel: 'Selecione um canal',
|
||||||
Send: 'Enviar',
|
Send: 'Enviar',
|
||||||
Send_audio_message: 'Enviar mensagem de áudio',
|
Send_audio_message: 'Enviar mensagem de áudio',
|
||||||
Send_message: 'Enviar mensagem',
|
Send_message: 'Enviar mensagem',
|
||||||
|
@ -435,6 +441,7 @@ export default {
|
||||||
Username: 'Usuário',
|
Username: 'Usuário',
|
||||||
Username_or_email: 'Usuário ou email',
|
Username_or_email: 'Usuário ou email',
|
||||||
Uses_server_configuration: 'Usar configuração do servidor',
|
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_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.',
|
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',
|
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_in_preview_mode: 'Está é uma prévia do canal',
|
||||||
You_are_offline: 'Você está offline',
|
You_are_offline: 'Você está offline',
|
||||||
You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`',
|
You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`',
|
||||||
|
Your_message: 'Sua mensagem',
|
||||||
You_colon: 'Você: ',
|
You_colon: 'Você: ',
|
||||||
you_were_mentioned: 'você foi mencionado',
|
you_were_mentioned: 'você foi mencionado',
|
||||||
You_were_removed_from_channel: 'Você foi removido de {{channel}}',
|
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
|
cardStyle
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const CreateDiscussionStack = createStackNavigator({
|
||||||
|
CreateDiscussionView: {
|
||||||
|
getScreen: () => require('./views/CreateDiscussionView').default
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
defaultNavigationOptions: defaultHeader,
|
||||||
|
cardStyle
|
||||||
|
});
|
||||||
|
|
||||||
const InsideStackModal = createStackNavigator({
|
const InsideStackModal = createStackNavigator({
|
||||||
Main: ChatsDrawer,
|
Main: ChatsDrawer,
|
||||||
NewMessageStack,
|
NewMessageStack,
|
||||||
AttachmentStack,
|
AttachmentStack,
|
||||||
ModalBlockStack,
|
ModalBlockStack,
|
||||||
|
CreateDiscussionStack,
|
||||||
JitsiMeetView: {
|
JitsiMeetView: {
|
||||||
getScreen: () => require('./views/JitsiMeetView').default
|
getScreen: () => require('./views/JitsiMeetView').default
|
||||||
}
|
}
|
||||||
|
@ -438,6 +448,7 @@ const ModalSwitch = createSwitchNavigator({
|
||||||
RoomActionsStack,
|
RoomActionsStack,
|
||||||
SettingsStack,
|
SettingsStack,
|
||||||
ModalBlockStack,
|
ModalBlockStack,
|
||||||
|
CreateDiscussionStack,
|
||||||
AuthLoading: () => null
|
AuthLoading: () => null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -605,6 +605,16 @@ const RocketChat = {
|
||||||
// RC 0.59.0
|
// RC 0.59.0
|
||||||
return this.sdk.post('im.create', { username });
|
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) {
|
joinRoom(roomId, type) {
|
||||||
// TODO: join code
|
// TODO: join code
|
||||||
// RC 0.48.0
|
// 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 activeUsers from './activeUsers';
|
||||||
import usersTyping from './usersTyping';
|
import usersTyping from './usersTyping';
|
||||||
import inviteLinks from './inviteLinks';
|
import inviteLinks from './inviteLinks';
|
||||||
|
import createDiscussion from './createDiscussion';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
|
@ -34,5 +35,6 @@ export default combineReducers({
|
||||||
customEmojis,
|
customEmojis,
|
||||||
activeUsers,
|
activeUsers,
|
||||||
usersTyping,
|
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 state from './state';
|
||||||
import deepLinking from './deepLinking';
|
import deepLinking from './deepLinking';
|
||||||
import inviteLinks from './inviteLinks';
|
import inviteLinks from './inviteLinks';
|
||||||
|
import createDiscussion from './createDiscussion';
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield all([
|
yield all([
|
||||||
|
@ -21,7 +22,8 @@ const root = function* root() {
|
||||||
selectServer(),
|
selectServer(),
|
||||||
state(),
|
state(),
|
||||||
deepLinking(),
|
deepLinking(),
|
||||||
inviteLinks()
|
inviteLinks(),
|
||||||
|
createDiscussion()
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,11 @@ export const initTabletNav = (setState) => {
|
||||||
setState({ showModal: true });
|
setState({ showModal: true });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (routeName === 'CreateDiscussionView') {
|
||||||
|
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||||
|
setState({ showModal: true });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (routeName === 'RoomView') {
|
if (routeName === 'RoomView') {
|
||||||
const resetAction = StackActions.reset({
|
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 { withTheme } from '../theme';
|
||||||
import { themedHeader } from '../utils/navigation';
|
import { themedHeader } from '../utils/navigation';
|
||||||
import { getUserSelector } from '../selectors/login';
|
import { getUserSelector } from '../selectors/login';
|
||||||
|
import Navigation from '../lib/Navigation';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
safeAreaView: {
|
safeAreaView: {
|
||||||
|
@ -33,7 +34,10 @@ const styles = StyleSheet.create({
|
||||||
marginLeft: 60
|
marginLeft: 60
|
||||||
},
|
},
|
||||||
createChannelButton: {
|
createChannelButton: {
|
||||||
marginVertical: 25
|
marginTop: 25
|
||||||
|
},
|
||||||
|
createDiscussionButton: {
|
||||||
|
marginBottom: 25
|
||||||
},
|
},
|
||||||
createChannelContainer: {
|
createChannelContainer: {
|
||||||
height: 46,
|
height: 46,
|
||||||
|
@ -42,7 +46,7 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
createChannelIcon: {
|
createChannelIcon: {
|
||||||
marginLeft: 18,
|
marginLeft: 18,
|
||||||
marginRight: 15
|
marginRight: 16
|
||||||
},
|
},
|
||||||
createChannelText: {
|
createChannelText: {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
|
@ -142,6 +146,10 @@ class NewMessageView extends React.Component {
|
||||||
navigation.navigate('SelectedUsersViewCreateChannel', { nextActionID: 'CREATE_CHANNEL', title: I18n.t('Select_Users') });
|
navigation.navigate('SelectedUsersViewCreateChannel', { nextActionID: 'CREATE_CHANNEL', title: I18n.t('Select_Users') });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createDiscussion = () => {
|
||||||
|
Navigation.navigate('CreateDiscussionView');
|
||||||
|
}
|
||||||
|
|
||||||
renderHeader = () => {
|
renderHeader = () => {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
|
@ -154,10 +162,21 @@ class NewMessageView extends React.Component {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
>
|
>
|
||||||
<View style={[sharedStyles.separatorVertical, styles.createChannelContainer, { borderColor: themes[theme].separatorColor }]}>
|
<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>
|
<Text style={[styles.createChannelText, { color: themes[theme].tintColor }]}>{I18n.t('Create_Channel')}</Text>
|
||||||
</View>
|
</View>
|
||||||
</Touch>
|
</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>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue