diff --git a/app/constants/settings.js b/app/constants/settings.js
index f01c79ae2..122d38f41 100644
--- a/app/constants/settings.js
+++ b/app/constants/settings.js
@@ -50,6 +50,9 @@ export default {
CROWD_Enable: {
type: 'valueAsBoolean'
},
+ DirectMesssage_maxUsers: {
+ type: 'valueAsNumber'
+ },
Accounts_Directory_DefaultView: {
type: 'valueAsString'
},
diff --git a/app/containers/RoomTypeIcon.js b/app/containers/RoomTypeIcon.js
index 6933f3582..4e4d0b1d4 100644
--- a/app/containers/RoomTypeIcon.js
+++ b/app/containers/RoomTypeIcon.js
@@ -15,7 +15,7 @@ const styles = StyleSheet.create({
});
const RoomTypeIcon = React.memo(({
- type, size, style, theme
+ type, size, isGroupChat, style, theme
}) => {
if (!type) {
return null;
@@ -31,6 +31,9 @@ const RoomTypeIcon = React.memo(({
if (type === 'c') {
return ;
} if (type === 'd') {
+ if (isGroupChat) {
+ return ;
+ }
return ;
} if (type === 'l') {
return ;
@@ -41,6 +44,7 @@ const RoomTypeIcon = React.memo(({
RoomTypeIcon.propTypes = {
theme: PropTypes.string,
type: PropTypes.string,
+ isGroupChat: PropTypes.bool,
size: PropTypes.number,
style: PropTypes.object
};
diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js
index c767abaab..ba77ab0aa 100644
--- a/app/i18n/locales/en.js
+++ b/app/i18n/locales/en.js
@@ -156,6 +156,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_Direct_Messages: 'Create Direct Messages',
Create_Discussion: 'Create Discussion',
Created_snippet: 'Created a snippet',
Create_a_new_workspace: 'Create a new workspace',
@@ -263,6 +264,7 @@ export default {
Logging_out: 'Logging out.',
Logout: 'Logout',
Max_number_of_uses: 'Max number of uses',
+ Max_number_of_users_allowed_is_number: 'Max number of users allowed is {{maxUsers}}',
members: 'members',
Members: 'Members',
Mentioned_Messages: 'Mentioned Messages',
diff --git a/app/i18n/locales/pt-BR.js b/app/i18n/locales/pt-BR.js
index ce010f49f..768fdbf31 100644
--- a/app/i18n/locales/pt-BR.js
+++ b/app/i18n/locales/pt-BR.js
@@ -153,6 +153,7 @@ export default {
Permalink: 'Link-Permanente',
Create_account: 'Criar conta',
Create_Channel: 'Criar Canal',
+ Create_Direct_Messages: 'Criar Mensagens Diretas',
Create_Discussion: 'Criar Discussão',
Created_snippet: 'Criou um snippet',
Create_a_new_workspace: 'Criar nova área de trabalho',
@@ -247,6 +248,7 @@ export default {
Logout: 'Sair',
Logging_out: 'Saindo.',
Max_number_of_uses: 'Número máximo de usos',
+ Max_number_of_users_allowed_is_number: 'Número máximo de usuários é {{maxUsers}}',
Members: 'Membros',
Mentioned_Messages: 'Mensagens mencionadas',
mentioned: 'mencionado',
diff --git a/app/lib/database/model/Subscription.js b/app/lib/database/model/Subscription.js
index d40193c52..37bab8c08 100644
--- a/app/lib/database/model/Subscription.js
+++ b/app/lib/database/model/Subscription.js
@@ -91,4 +91,8 @@ export default class Subscription extends Model {
@field('hide_unread_status') hideUnreadStatus;
@json('sys_mes', sanitizer) sysMes;
+
+ @json('uids', sanitizer) uids;
+
+ @json('usernames', sanitizer) usernames;
}
diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js
index 14313585e..c8d815245 100644
--- a/app/lib/database/model/migrations.js
+++ b/app/lib/database/model/migrations.js
@@ -62,6 +62,18 @@ export default schemaMigrations({
]
})
]
+ },
+ {
+ toVersion: 7,
+ steps: [
+ addColumns({
+ table: 'subscriptions',
+ columns: [
+ { name: 'uids', type: 'string', isOptional: true },
+ { name: 'usernames', type: 'string', isOptional: true }
+ ]
+ })
+ ]
}
]
});
diff --git a/app/lib/database/schema/app.js b/app/lib/database/schema/app.js
index 20741b573..a16cb547d 100644
--- a/app/lib/database/schema/app.js
+++ b/app/lib/database/schema/app.js
@@ -1,7 +1,7 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
- version: 6,
+ version: 7,
tables: [
tableSchema({
name: 'subscriptions',
@@ -40,7 +40,9 @@ export default appSchema({
{ name: 'auto_translate', type: 'boolean', isOptional: true },
{ name: 'auto_translate_language', type: 'string' },
{ name: 'hide_unread_status', type: 'boolean', isOptional: true },
- { name: 'sys_mes', type: 'string', isOptional: true }
+ { name: 'sys_mes', type: 'string', isOptional: true },
+ { name: 'uids', type: 'string', isOptional: true },
+ { name: 'usernames', type: 'string', isOptional: true }
]
}),
tableSchema({
diff --git a/app/lib/methods/helpers/mergeSubscriptionsRooms.js b/app/lib/methods/helpers/mergeSubscriptionsRooms.js
index 6af040f5b..ba26294ce 100644
--- a/app/lib/methods/helpers/mergeSubscriptionsRooms.js
+++ b/app/lib/methods/helpers/mergeSubscriptionsRooms.js
@@ -21,6 +21,8 @@ export const merge = (subscription, room) => {
subscription.archived = room.archived || false;
subscription.joinCodeRequired = room.joinCodeRequired;
subscription.jitsiTimeout = room.jitsiTimeout;
+ subscription.usernames = room.usernames;
+ subscription.uids = room.uids;
}
subscription.ro = room.ro;
subscription.broadcast = room.broadcast;
diff --git a/app/lib/methods/subscriptions/rooms.js b/app/lib/methods/subscriptions/rooms.js
index 7ba99097a..e954e6f14 100644
--- a/app/lib/methods/subscriptions/rooms.js
+++ b/app/lib/methods/subscriptions/rooms.js
@@ -71,7 +71,9 @@ const createOrUpdateSubscription = async(subscription, room) => {
jitsiTimeout: s.jitsiTimeout,
autoTranslate: s.autoTranslate,
autoTranslateLanguage: s.autoTranslateLanguage,
- lastMessage: s.lastMessage
+ lastMessage: s.lastMessage,
+ usernames: s.usernames,
+ uids: s.uids
};
} catch (error) {
try {
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 15c5f9040..66b50b489 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -607,6 +607,14 @@ const RocketChat = {
return this.sdk.post('im.create', { username });
},
+ createGroupChat() {
+ let { users } = reduxStore.getState().selectedUsers;
+ users = users.map(u => u.name);
+
+ // RC 3.1.0
+ return this.sdk.methodCall('createDirectMessage', ...users);
+ },
+
createDiscussion({
prid, pmid, t_name, reply, users
}) {
@@ -784,12 +792,27 @@ const RocketChat = {
// RC 0.72.0
return this.sdk.get('rooms.info', { roomId });
},
- getRoomMemberId(rid, currentUserId) {
- if (rid === `${ currentUserId }${ currentUserId }`) {
- return currentUserId;
+
+ getUidDirectMessage(room, userId) {
+ // legacy method
+ if (!room.uids && room.rid && room.t === 'd') {
+ return room.rid.replace(userId, '').trim();
}
- return rid.replace(currentUserId, '').trim();
+
+ if (RocketChat.isGroupChat(room)) {
+ return false;
+ }
+
+ const me = room && room.uids && room.uids.find(uid => uid === userId);
+ const other = room && room.uids && room.uids.filter(uid => uid !== userId);
+
+ return other && other.length ? other[0] : me;
},
+
+ isGroupChat(room) {
+ return (room.uids && room.uids.length > 2) || (room.usernames && room.usernames.length > 2);
+ },
+
toggleBlockUser(rid, blocked, block) {
if (block) {
// RC 0.49.0
@@ -1121,9 +1144,16 @@ const RocketChat = {
},
getRoomTitle(room) {
const { UI_Use_Real_Name: useRealName } = reduxStore.getState().settings;
+ const { username } = reduxStore.getState().login.user;
+ if (RocketChat.isGroupChat(room) && !(room.name && room.name.length)) {
+ return room.usernames.filter(u => u !== username).sort((u1, u2) => u1.localeCompare(u2)).join(', ');
+ }
return ((room.prid || useRealName) && room.fname) || room.name;
},
getRoomAvatar(room) {
+ if (RocketChat.isGroupChat(room)) {
+ return room.uids.length + room.usernames.join();
+ }
return room.prid ? room.fname : room.name;
},
diff --git a/app/presentation/RoomItem/TypeIcon.js b/app/presentation/RoomItem/TypeIcon.js
index 0f8079427..19537c07e 100644
--- a/app/presentation/RoomItem/TypeIcon.js
+++ b/app/presentation/RoomItem/TypeIcon.js
@@ -6,19 +6,20 @@ import RoomTypeIcon from '../../containers/RoomTypeIcon';
import styles from './styles';
const TypeIcon = React.memo(({
- theme, type, prid, status
+ theme, type, prid, status, isGroupChat
}) => {
- if (type === 'd') {
+ if (type === 'd' && !isGroupChat) {
return ;
}
- return ;
+ return ;
});
TypeIcon.propTypes = {
theme: PropTypes.string,
type: PropTypes.string,
status: PropTypes.string,
- prid: PropTypes.string
+ prid: PropTypes.string,
+ isGroupChat: PropTypes.bool
};
export default TypeIcon;
diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js
index 2db35a8f8..a284ca956 100644
--- a/app/presentation/RoomItem/index.js
+++ b/app/presentation/RoomItem/index.js
@@ -40,12 +40,11 @@ const arePropsEqual = (oldProps, newProps) => {
};
const RoomItem = React.memo(({
- onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar, useRealName, getUserPresence, theme
+ onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar, useRealName, getUserPresence, isGroupChat, theme
}) => {
useEffect(() => {
- if (type === 'd' && rid) {
- const uid = rid.replace(userId, '');
- getUserPresence(uid);
+ if (type === 'd') {
+ getUserPresence(id);
}
}, []);
@@ -104,9 +103,9 @@ const RoomItem = React.memo(({
- {name}
- @{username}
+ {name}
+ @{username}
{icon ? : null}
diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js
index 7047505de..86a5b613b 100644
--- a/app/sagas/createChannel.js
+++ b/app/sagas/createChannel.js
@@ -5,11 +5,18 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes';
import { createChannelSuccess, createChannelFailure } from '../actions/createChannel';
+import { showErrorAlert } from '../utils/info';
import RocketChat from '../lib/rocketchat';
+import Navigation from '../lib/Navigation';
import database from '../lib/database';
+import I18n from '../i18n';
-const create = function* create(data) {
- return yield RocketChat.createChannel(data);
+const createChannel = function createChannel(data) {
+ return RocketChat.createChannel(data);
+};
+
+const createGroupChat = function createGroupChat() {
+ return RocketChat.createGroupChat();
};
const handleRequest = function* handleRequest({ data }) {
@@ -18,7 +25,13 @@ const handleRequest = function* handleRequest({ data }) {
if (!auth) {
yield take(LOGIN.SUCCESS);
}
- const sub = yield call(create, data);
+
+ let sub;
+ if (data.group) {
+ sub = yield call(createGroupChat);
+ } else {
+ sub = yield call(createChannel, data);
+ }
try {
const db = database.active;
@@ -39,8 +52,22 @@ const handleRequest = function* handleRequest({ data }) {
}
};
+const handleSuccess = function handleSuccess({ data }) {
+ const { rid, t } = data;
+ Navigation.navigate('RoomView', { rid, t, name: RocketChat.getRoomTitle(data) });
+};
+
+const handleFailure = function handleFailure({ err }) {
+ setTimeout(() => {
+ const msg = err.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
+ showErrorAlert(msg);
+ }, 300);
+};
+
const root = function* root() {
yield takeLatest(CREATE_CHANNEL.REQUEST, handleRequest);
+ yield takeLatest(CREATE_CHANNEL.SUCCESS, handleSuccess);
+ yield takeLatest(CREATE_CHANNEL.FAILURE, handleFailure);
};
export default root;
diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js
index 02ecfc66d..4709afd6c 100644
--- a/app/views/CreateChannelView.js
+++ b/app/views/CreateChannelView.js
@@ -16,7 +16,6 @@ import KeyboardView from '../presentation/KeyboardView';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import I18n from '../i18n';
import UserItem from '../presentation/UserItem';
-import { showErrorAlert } from '../utils/info';
import { CustomHeaderButtons, Item } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
import { SWITCH_TRACK_COLOR, themes } from '../constants/colors';
@@ -100,7 +99,6 @@ class CreateChannelView extends React.Component {
error: PropTypes.object,
failure: PropTypes.bool,
isFetching: PropTypes.bool,
- result: PropTypes.object,
users: PropTypes.array.isRequired,
user: PropTypes.shape({
id: PropTypes.string,
@@ -125,9 +123,7 @@ class CreateChannelView extends React.Component {
const {
channelName, type, readOnly, broadcast
} = this.state;
- const {
- error, failure, isFetching, result, users, theme
- } = this.props;
+ const { users, isFetching, theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
@@ -143,43 +139,15 @@ class CreateChannelView extends React.Component {
if (nextState.broadcast !== broadcast) {
return true;
}
- if (nextProps.failure !== failure) {
- return true;
- }
if (nextProps.isFetching !== isFetching) {
return true;
}
- if (!equal(nextProps.error, error)) {
- return true;
- }
- if (!equal(nextProps.result, result)) {
- return true;
- }
if (!equal(nextProps.users, users)) {
return true;
}
return false;
}
- componentDidUpdate(prevProps) {
- const {
- isFetching, failure, error, result, navigation
- } = this.props;
-
- if (!isFetching && isFetching !== prevProps.isFetching) {
- setTimeout(() => {
- if (failure) {
- const msg = error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
- showErrorAlert(msg);
- } else {
- const { type } = this.state;
- const { rid, name } = result;
- navigation.navigate('RoomView', { rid, name, t: type ? 'p' : 'c' });
- }
- }, 300);
- }
- }
-
onChangeText = (channelName) => {
const { navigation } = this.props;
navigation.setParams({ showSubmit: channelName.trim().length > 0 });
@@ -365,10 +333,7 @@ class CreateChannelView extends React.Component {
const mapStateToProps = state => ({
baseUrl: state.server.server,
- error: state.createChannel.error,
- failure: state.createChannel.failure,
isFetching: state.createChannel.isFetching,
- result: state.createChannel.result,
users: state.selectedUsers.users,
user: getUserSelector(state)
});
diff --git a/app/views/DirectoryView/index.js b/app/views/DirectoryView/index.js
index e54f966c9..3828a7474 100644
--- a/app/views/DirectoryView/index.js
+++ b/app/views/DirectoryView/index.js
@@ -180,7 +180,10 @@ class DirectoryView extends React.Component {
let style;
if (index === data.length - 1) {
- style = sharedStyles.separatorBottom;
+ style = {
+ ...sharedStyles.separatorBottom,
+ borderColor: themes[theme].separatorColor
+ };
}
const commonProps = {
diff --git a/app/views/NewMessageView.js b/app/views/NewMessageView.js
index 69f61f1d6..cecd09886 100644
--- a/app/views/NewMessageView.js
+++ b/app/views/NewMessageView.js
@@ -25,6 +25,7 @@ import { withTheme } from '../theme';
import { themedHeader } from '../utils/navigation';
import { getUserSelector } from '../selectors/login';
import Navigation from '../lib/Navigation';
+import { createChannelRequest } from '../actions/createChannel';
const styles = StyleSheet.create({
safeAreaView: {
@@ -33,24 +34,21 @@ const styles = StyleSheet.create({
separator: {
marginLeft: 60
},
- createChannelButton: {
- marginTop: 25
- },
- createDiscussionButton: {
- marginBottom: 25
- },
- createChannelContainer: {
+ button: {
height: 46,
flexDirection: 'row',
alignItems: 'center'
},
- createChannelIcon: {
+ buttonIcon: {
marginLeft: 18,
marginRight: 16
},
- createChannelText: {
+ buttonText: {
fontSize: 17,
...sharedStyles.textRegular
+ },
+ buttonContainer: {
+ paddingVertical: 25
}
});
@@ -68,6 +66,8 @@ class NewMessageView extends React.Component {
id: PropTypes.string,
token: PropTypes.string
}),
+ createChannel: PropTypes.func,
+ maxUsers: PropTypes.number,
theme: PropTypes.string
};
@@ -143,7 +143,35 @@ class NewMessageView extends React.Component {
createChannel = () => {
const { navigation } = this.props;
- navigation.navigate('SelectedUsersViewCreateChannel', { nextActionID: 'CREATE_CHANNEL', title: I18n.t('Select_Users') });
+ navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
+ }
+
+ createGroupChat = () => {
+ const { createChannel, maxUsers, navigation } = this.props;
+ navigation.navigate('SelectedUsersViewCreateChannel', {
+ nextAction: () => createChannel({ group: true }),
+ buttonText: I18n.t('Create'),
+ maxUsers
+ });
+ }
+
+ renderButton = ({
+ onPress, testID, title, icon, first
+ }) => {
+ const { theme } = this.props;
+ return (
+
+
+
+ {title}
+
+
+ );
}
createDiscussion = () => {
@@ -151,32 +179,31 @@ class NewMessageView extends React.Component {
}
renderHeader = () => {
- const { theme } = this.props;
+ const { maxUsers, theme } = this.props;
return (
this.onSearchChangeText(text)} testID='new-message-view-search' />
-
-
-
- {I18n.t('Create_Channel')}
-
-
-
-
-
- {I18n.t('Create_Discussion')}
-
-
+
+ {this.renderButton({
+ onPress: this.createChannel,
+ title: I18n.t('Create_Channel'),
+ icon: 'hashtag',
+ testID: 'new-message-view-create-channel',
+ first: true
+ })}
+ {maxUsers > 2 ? this.renderButton({
+ onPress: this.createGroupChat,
+ title: I18n.t('Create_Direct_Messages'),
+ icon: 'team',
+ testID: 'new-message-view-create-direct-message'
+ }) : null}
+ {this.renderButton({
+ onPress: this.createDiscussion,
+ title: I18n.t('Create_Discussion'),
+ icon: 'chat',
+ testID: 'new-message-view-create-discussion'
+ })}
+
);
}
@@ -248,7 +275,12 @@ class NewMessageView extends React.Component {
const mapStateToProps = state => ({
baseUrl: state.server.server,
+ maxUsers: state.settings.DirectMesssage_maxUsers || 1,
user: getUserSelector(state)
});
-export default connect(mapStateToProps)(withTheme(NewMessageView));
+const mapDispatchToProps = dispatch => ({
+ createChannel: params => dispatch(createChannelRequest(params))
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewMessageView));
diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js
index ba184f1d7..7fe261a84 100644
--- a/app/views/RoomActionsView/index.js
+++ b/app/views/RoomActionsView/index.js
@@ -8,6 +8,7 @@ import { SafeAreaView } from 'react-navigation';
import _ from 'lodash';
import Touch from '../../utils/touch';
+import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
import { leaveRoom as leaveRoomAction } from '../../actions/room';
import styles from './styles';
import sharedStyles from '../Styles';
@@ -49,6 +50,7 @@ class RoomActionsView extends React.Component {
}),
leaveRoom: PropTypes.func,
jitsiEnabled: PropTypes.bool,
+ setLoadingInvite: PropTypes.func,
theme: PropTypes.string
}
@@ -190,6 +192,7 @@ class RoomActionsView extends React.Component {
const {
rid, t, blocker
} = room;
+ const isGroupChat = RocketChat.isGroupChat(room);
const notificationsAction = {
icon: 'bell',
@@ -223,6 +226,7 @@ class RoomActionsView extends React.Component {
params: {
rid, t, room, member
},
+ disabled: isGroupChat,
testID: 'room-actions-info'
}],
renderItem: this.renderRoomInfo
@@ -286,7 +290,18 @@ class RoomActionsView extends React.Component {
});
}
- if (t === 'd') {
+ if (isGroupChat) {
+ sections[2].data.unshift({
+ icon: 'team',
+ name: I18n.t('Members'),
+ description: membersCount > 0 ? `${ membersCount } ${ I18n.t('members') }` : null,
+ route: 'RoomMembersView',
+ params: { rid, room },
+ testID: 'room-actions-members'
+ });
+ }
+
+ if (t === 'd' && !isGroupChat) {
sections.push({
data: [
{
@@ -320,9 +335,9 @@ class RoomActionsView extends React.Component {
name: I18n.t('Add_users'),
route: 'SelectedUsersView',
params: {
- nextActionID: 'ADD_USER',
rid,
- title: I18n.t('Add_users')
+ title: I18n.t('Add_users'),
+ nextAction: this.addUser
},
testID: 'room-actions-add-user'
});
@@ -369,14 +384,15 @@ class RoomActionsView extends React.Component {
updateRoomMember = async() => {
const { room } = this.state;
- const { rid } = room;
const { user } = this.props;
try {
- const roomUserId = RocketChat.getRoomMemberId(rid, user.id);
- const result = await RocketChat.getUserInfo(roomUserId);
- if (result.success) {
- this.setState({ member: result.user });
+ if (!RocketChat.isGroupChat(room)) {
+ const roomUserId = RocketChat.getUidDirectMessage(room, user.id);
+ const result = await RocketChat.getUserInfo(roomUserId);
+ if (result.success) {
+ this.setState({ member: result.user });
+ }
}
} catch (e) {
log(e);
@@ -384,6 +400,21 @@ class RoomActionsView extends React.Component {
}
}
+ addUser = async() => {
+ const { room } = this.state;
+ const { setLoadingInvite, navigation } = this.props;
+ const { rid } = room;
+ try {
+ setLoadingInvite(true);
+ await RocketChat.addUsersToRoom(rid);
+ navigation.pop();
+ } catch (e) {
+ log(e);
+ } finally {
+ setLoadingInvite(false);
+ }
+ }
+
toggleBlockUser = () => {
const { room } = this.state;
const { rid, blocker } = room;
@@ -432,41 +463,44 @@ class RoomActionsView extends React.Component {
const { name, t, topic } = room;
const { baseUrl, user, theme } = this.props;
+ const avatar = RocketChat.getRoomAvatar(room);
+
return (
- this.renderTouchableItem([
-
- {t === 'd' && member._id ? : null }
- ,
-
- {room.t === 'd'
- ? {room.fname}
- : (
-
-
- {room.prid ? room.fname : room.name}
-
- )
- }
-
- {room.t === 'd' && }
- ,
-
- ], item)
+ this.renderTouchableItem((
+ <>
+
+ {t === 'd' && member._id ? : null }
+
+
+ {room.t === 'd'
+ ? {room.fname}
+ : (
+
+
+ {room.prid ? room.fname : room.name}
+
+ )
+ }
+
+ {room.t === 'd' && }
+
+ {!item.disabled && }
+ >
+ ), item)
);
}
@@ -478,10 +512,11 @@ class RoomActionsView extends React.Component {
style={{ backgroundColor: themes[theme].backgroundColor }}
accessibilityLabel={item.name}
accessibilityTraits='button'
+ enabled={!item.disabled}
testID={item.testID}
theme={theme}
>
-
+
{subview}
@@ -491,15 +526,19 @@ class RoomActionsView extends React.Component {
renderItem = ({ item }) => {
const { theme } = this.props;
const colorDanger = { color: themes[theme].dangerColor };
- const subview = item.type === 'danger' ? [
- ,
- { item.name }
- ] : [
- ,
- { item.name },
- item.description ? { item.description } : null,
-
- ];
+ const subview = item.type === 'danger' ? (
+ <>
+
+ { item.name }
+ >
+ ) : (
+ <>
+
+ { item.name }
+ {item.description ? { item.description } : null}
+
+ >
+ );
return this.renderTouchableItem(subview, item);
}
@@ -542,7 +581,8 @@ const mapStateToProps = state => ({
});
const mapDispatchToProps = dispatch => ({
- leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t))
+ leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t)),
+ setLoadingInvite: loading => dispatch(setLoadingAction(loading))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RoomActionsView));
diff --git a/app/views/RoomActionsView/styles.js b/app/views/RoomActionsView/styles.js
index 1461cba37..8021dfb2a 100644
--- a/app/views/RoomActionsView/styles.js
+++ b/app/views/RoomActionsView/styles.js
@@ -14,9 +14,6 @@ export default StyleSheet.create({
flexDirection: 'row',
alignItems: 'center'
},
- sectionItemDisabled: {
- opacity: 0.3
- },
sectionItemIcon: {
width: 56,
textAlign: 'center'
diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js
index 610d49f6e..184109ea7 100644
--- a/app/views/RoomInfoView/index.js
+++ b/app/views/RoomInfoView/index.js
@@ -77,22 +77,21 @@ class RoomInfoView extends React.Component {
this.rid = props.navigation.getParam('rid');
this.t = props.navigation.getParam('t');
this.state = {
- room: room || {},
+ room: room || { rid: this.rid, t: this.t },
roomUser: roomUser || {},
parsedRoles: []
};
}
async componentDidMount() {
- const { roomUser } = this.state;
+ const { roomUser, room: roomState } = this.state;
if (this.t === 'd' && !_.isEmpty(roomUser)) {
return;
}
if (this.t === 'd') {
- const { user } = this.props;
- const roomUserId = RocketChat.getRoomMemberId(this.rid, user.id);
try {
+ const roomUserId = RocketChat.getUidDirectMessage(roomState);
const result = await RocketChat.getUserInfo(roomUserId);
if (result.success) {
const { roles } = result.user;
@@ -110,6 +109,7 @@ class RoomInfoView extends React.Component {
}
return;
}
+
const { navigation } = this.props;
let room = navigation.getParam('room');
if (room && room.observe) {
diff --git a/app/views/RoomView/Header/Header.js b/app/views/RoomView/Header/Header.js
index f85946656..2a668f0ed 100644
--- a/app/views/RoomView/Header/Header.js
+++ b/app/views/RoomView/Header/Header.js
@@ -125,7 +125,7 @@ HeaderTitle.propTypes = {
};
const Header = React.memo(({
- title, subtitle, type, status, usersTyping, width, height, prid, tmid, widthOffset, connecting, goRoomActionsView, theme
+ title, subtitle, type, status, usersTyping, width, height, prid, tmid, widthOffset, connecting, goRoomActionsView, roomUserId, theme
}) => {
const portrait = height > width;
let scale = 1;
@@ -146,7 +146,7 @@ const Header = React.memo(({
disabled={tmid}
>
-
+
{
- if (type === 'd') {
+const Icon = React.memo(({
+ roomUserId, type, status, theme
+}) => {
+ if (type === 'd' && roomUserId) {
return ;
}
let colorStyle = {};
- if (type === 'd') {
+ if (type === 'd' && roomUserId) {
colorStyle = { color: STATUS_COLORS[status] };
} else {
colorStyle = { color: isAndroid && theme === 'light' ? themes[theme].buttonText : themes[theme].auxiliaryText };
@@ -42,6 +44,8 @@ const Icon = React.memo(({ type, status, theme }) => {
icon = 'hashtag';
} else if (type === 'l') {
icon = 'livechat';
+ } else if (type === 'd') {
+ icon = 'team';
} else {
icon = 'lock';
}
@@ -62,6 +66,7 @@ const Icon = React.memo(({ type, status, theme }) => {
});
Icon.propTypes = {
+ roomUserId: PropTypes.string,
type: PropTypes.string,
status: PropTypes.string,
theme: PropTypes.string
diff --git a/app/views/RoomView/Header/index.js b/app/views/RoomView/Header/index.js
index a6497a074..7e91d3afe 100644
--- a/app/views/RoomView/Header/index.js
+++ b/app/views/RoomView/Header/index.js
@@ -23,6 +23,7 @@ class RoomHeaderView extends Component {
statusText: PropTypes.string,
connecting: PropTypes.bool,
theme: PropTypes.string,
+ roomUserId: PropTypes.string,
widthOffset: PropTypes.number,
goRoomActionsView: PropTypes.func
};
@@ -69,7 +70,7 @@ class RoomHeaderView extends Component {
render() {
const {
- window, title, subtitle, type, prid, tmid, widthOffset, status = 'offline', statusText, connecting, usersTyping, goRoomActionsView, theme
+ window, title, subtitle, type, prid, tmid, widthOffset, status = 'offline', statusText, connecting, usersTyping, goRoomActionsView, roomUserId, theme
} = this.props;
return (
@@ -85,6 +86,7 @@ class RoomHeaderView extends Component {
theme={theme}
usersTyping={usersTyping}
widthOffset={widthOffset}
+ roomUserId={roomUserId}
goRoomActionsView={goRoomActionsView}
connecting={connecting}
/>
@@ -95,13 +97,12 @@ class RoomHeaderView extends Component {
const mapStateToProps = (state, ownProps) => {
let status;
let statusText;
- const { rid, type } = ownProps;
+ const { roomUserId, type } = ownProps;
if (type === 'd') {
const user = getUserSelector(state);
if (user.id) {
- const userId = rid.replace(user.id, '').trim();
- if (state.activeUsers[userId]) {
- ({ status, statusText } = state.activeUsers[userId]);
+ if (state.activeUsers[roomUserId]) {
+ ({ status, statusText } = state.activeUsers[roomUserId]);
}
}
}
diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js
index d43d4c914..41d14e6f5 100644
--- a/app/views/RoomView/index.js
+++ b/app/views/RoomView/index.js
@@ -84,6 +84,7 @@ class RoomView extends React.Component {
const toggleFollowThread = navigation.getParam('toggleFollowThread', () => {});
const goRoomActionsView = navigation.getParam('goRoomActionsView', () => {});
const unreadsCount = navigation.getParam('unreadsCount', null);
+ const roomUserId = navigation.getParam('roomUserId');
if (!rid) {
return {
...themedHeader(screenProps.theme)
@@ -100,6 +101,7 @@ class RoomView extends React.Component {
subtitle={subtitle}
type={t}
widthOffset={tmid ? 95 : 130}
+ roomUserId={roomUserId}
goRoomActionsView={goRoomActionsView}
/>
),
@@ -382,13 +384,16 @@ class RoomView extends React.Component {
getRoomMember = async() => {
const { room } = this.state;
- const { rid, t } = room;
+ const { t } = room;
- if (t === 'd') {
- const { user } = this.props;
+ if (t === 'd' && !RocketChat.isGroupChat(room)) {
+ const { user, navigation } = this.props;
try {
- const roomUserId = RocketChat.getRoomMemberId(rid, user.id);
+ const roomUserId = RocketChat.getUidDirectMessage(room, user.id);
+
+ navigation.setParams({ roomUserId });
+
const result = await RocketChat.getUserInfo(roomUserId);
if (result.success) {
return result.user;
diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js
index bc9a09bbd..d8b6e4728 100644
--- a/app/views/RoomsListView/index.js
+++ b/app/views/RoomsListView/index.js
@@ -412,7 +412,9 @@ class RoomsListView extends React.Component {
key: item._id,
rid: item.rid,
type: item.t,
- prid: item.prid
+ prid: item.prid,
+ uids: item.uids,
+ usernames: item.usernames
}));
// unread
@@ -526,6 +528,11 @@ class RoomsListView extends React.Component {
getUserPresence = uid => RocketChat.getUserPresence(uid)
+ getUidDirectMessage = (room) => {
+ const { user: { id } } = this.props;
+ return RocketChat.getUidDirectMessage(room, id);
+ }
+
goRoom = (item) => {
const { navigation } = this.props;
this.cancelSearch();
@@ -535,6 +542,7 @@ class RoomsListView extends React.Component {
name: this.getRoomTitle(item),
t: item.t,
prid: item.prid,
+ roomUserId: this.getUidDirectMessage(item),
room: item
});
}
@@ -764,7 +772,8 @@ class RoomsListView extends React.Component {
theme,
split
} = this.props;
- const id = item.rid.replace(userId, '').trim();
+ const id = this.getUidDirectMessage(item);
+ const isGroupChat = RocketChat.isGroupChat(item);
return (
);
};
diff --git a/app/views/SelectedUsersView.js b/app/views/SelectedUsersView.js
index 530b7e3d7..1a1ac044d 100644
--- a/app/views/SelectedUsersView.js
+++ b/app/views/SelectedUsersView.js
@@ -7,14 +7,10 @@ import equal from 'deep-equal';
import { orderBy } from 'lodash';
import { Q } from '@nozbe/watermelondb';
-import {
- addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction
-} from '../actions/selectedUsers';
import database from '../lib/database';
import RocketChat from '../lib/rocketchat';
import UserItem from '../presentation/UserItem';
import Loading from '../containers/Loading';
-import debounce from '../utils/debounce';
import I18n from '../i18n';
import log from '../utils/log';
import SearchBox from '../containers/SearchBox';
@@ -26,6 +22,12 @@ import { animateNextTransition } from '../utils/layoutAnimation';
import { withTheme } from '../theme';
import { themedHeader } from '../utils/navigation';
import { getUserSelector } from '../selectors/login';
+import {
+ reset as resetAction,
+ addUser as addUserAction,
+ removeUser as removeUserAction
+} from '../actions/selectedUsers';
+import { showErrorAlert } from '../utils/info';
const styles = StyleSheet.create({
safeAreaView: {
@@ -38,47 +40,55 @@ const styles = StyleSheet.create({
class SelectedUsersView extends React.Component {
static navigationOptions = ({ navigation, screenProps }) => {
- const title = navigation.getParam('title');
+ const title = navigation.getParam('title', I18n.t('Select_Users'));
+ const buttonText = navigation.getParam('buttonText', I18n.t('Next'));
+ const showButton = navigation.getParam('showButton', false);
+ const maxUsers = navigation.getParam('maxUsers');
const nextAction = navigation.getParam('nextAction', () => {});
return {
...themedHeader(screenProps.theme),
title,
headerRight: (
-
-
-
+ (!maxUsers || showButton) && (
+
+
+
+ )
)
};
}
static propTypes = {
- navigation: PropTypes.object,
baseUrl: PropTypes.string,
addUser: PropTypes.func.isRequired,
removeUser: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
users: PropTypes.array,
loading: PropTypes.bool,
- setLoadingInvite: PropTypes.func,
user: PropTypes.shape({
id: PropTypes.string,
- token: PropTypes.string
+ token: PropTypes.string,
+ username: PropTypes.string,
+ name: PropTypes.string
}),
+ navigation: PropTypes.object,
theme: PropTypes.string
};
constructor(props) {
super(props);
this.init();
+
+ const maxUsers = props.navigation.getParam('maxUsers');
this.state = {
+ maxUsers,
search: [],
chats: []
};
- }
-
- componentDidMount() {
- const { navigation } = this.props;
- navigation.setParams({ nextAction: this.nextAction });
+ const { user } = this.props;
+ if (this.isGroupChat()) {
+ props.addUser({ _id: user.id, name: user.username, fname: user.name });
+ }
}
shouldComponentUpdate(nextProps, nextState) {
@@ -102,6 +112,19 @@ class SelectedUsersView extends React.Component {
return false;
}
+ componentDidUpdate(prevProps) {
+ if (this.isGroupChat()) {
+ const { users, navigation } = this.props;
+ if (prevProps.users.length !== users.length) {
+ if (users.length) {
+ navigation.setParams({ showButton: true });
+ } else {
+ navigation.setParams({ showButton: false });
+ }
+ }
+ }
+ }
+
componentWillUnmount() {
const { reset } = this.props;
reset();
@@ -132,30 +155,6 @@ class SelectedUsersView extends React.Component {
this.search(text);
}
- nextAction = async() => {
- const { navigation, setLoadingInvite } = this.props;
- const nextActionID = navigation.getParam('nextActionID');
- if (nextActionID === 'CREATE_CHANNEL') {
- navigation.navigate('CreateChannelView');
- } else {
- const rid = navigation.getParam('rid');
- try {
- setLoadingInvite(true);
- await RocketChat.addUsersToRoom(rid);
- navigation.pop();
- } catch (e) {
- log(e);
- } finally {
- setLoadingInvite(false);
- }
- }
- }
-
- // eslint-disable-next-line react/sort-comp
- updateState = debounce(() => {
- this.forceUpdate();
- }, 1000);
-
search = async(text) => {
const result = await RocketChat.search({ text, filterRooms: false });
this.setState({
@@ -163,16 +162,33 @@ class SelectedUsersView extends React.Component {
});
}
+ isGroupChat = () => {
+ const { maxUsers } = this.state;
+ return maxUsers > 2;
+ }
+
isChecked = (username) => {
const { users } = this.props;
return users.findIndex(el => el.name === username) !== -1;
}
toggleUser = (user) => {
- const { addUser, removeUser } = this.props;
+ const { maxUsers } = this.state;
+ const {
+ addUser, removeUser, users, user: { username }
+ } = this.props;
+
+ // Disallow removing self user from the direct message group
+ if (this.isGroupChat() && username === user.name) {
+ return;
+ }
animateNextTransition();
if (!this.isChecked(user.name)) {
+ if (this.isGroupChat() && users.length === maxUsers) {
+ return showErrorAlert(I18n.t('Max_number_of_users_allowed_is_number', { maxUsers }), I18n.t('Oops'));
+ }
+
addUser(user);
} else {
removeUser(user);
@@ -274,9 +290,14 @@ class SelectedUsersView extends React.Component {
renderList = () => {
const { search, chats } = this.state;
const { theme } = this.props;
+
+ const data = (search.length > 0 ? search : chats)
+ // filter DM between multiple users
+ .filter(sub => !RocketChat.isGroupChat(sub));
+
return (
0 ? search : chats}
+ data={data}
extraData={this.props}
keyExtractor={item => item._id}
renderItem={this.renderItem}
@@ -315,8 +336,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
addUser: user => dispatch(addUserAction(user)),
removeUser: user => dispatch(removeUserAction(user)),
- reset: () => dispatch(resetAction()),
- setLoadingInvite: loading => dispatch(setLoadingAction(loading))
+ reset: () => dispatch(resetAction())
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(SelectedUsersView));