feat: capability to enable/disable writing in rooms read only (#5298)
* feat: capability to enable writing in rooms read only * minor tweak * add shallowEqual * change the message box properly when the user is enable to write
This commit is contained in:
parent
60a352beb4
commit
d6c37bf4a2
|
@ -39,6 +39,7 @@ export interface IRoom {
|
||||||
default?: boolean;
|
default?: boolean;
|
||||||
featured?: boolean;
|
featured?: boolean;
|
||||||
muted?: string[];
|
muted?: string[];
|
||||||
|
unmuted?: string[];
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
ignored?: string;
|
ignored?: string;
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@ export interface ISubscription {
|
||||||
archived: boolean;
|
archived: boolean;
|
||||||
joinCodeRequired?: boolean;
|
joinCodeRequired?: boolean;
|
||||||
muted?: string[];
|
muted?: string[];
|
||||||
|
unmuted?: string[];
|
||||||
ignored?: string[];
|
ignored?: string[];
|
||||||
broadcast?: boolean;
|
broadcast?: boolean;
|
||||||
prid?: string;
|
prid?: string;
|
||||||
|
@ -111,7 +112,8 @@ export interface ISubscription {
|
||||||
uploads: RelationModified<TUploadModel>;
|
uploads: RelationModified<TUploadModel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TSubscriptionModel = ISubscription & Model & {
|
export type TSubscriptionModel = ISubscription &
|
||||||
|
Model & {
|
||||||
asPlain: () => ISubscription;
|
asPlain: () => ISubscription;
|
||||||
};
|
};
|
||||||
export type TSubscription = TSubscriptionModel | ISubscription;
|
export type TSubscription = TSubscriptionModel | ISubscription;
|
||||||
|
|
|
@ -754,5 +754,9 @@
|
||||||
"Supported_versions_expired_title": "{{workspace_name}} is running an unsupported version of Rocket.Chat",
|
"Supported_versions_expired_title": "{{workspace_name}} is running an unsupported version of Rocket.Chat",
|
||||||
"Supported_versions_expired_description": "An admin needs to update the workspace to a supported version in order to reenable access from mobile and desktop apps.",
|
"Supported_versions_expired_description": "An admin needs to update the workspace to a supported version in order to reenable access from mobile and desktop apps.",
|
||||||
"Supported_versions_warning_update_required": "Update required",
|
"Supported_versions_warning_update_required": "Update required",
|
||||||
|
"The_user_wont_be_able_to_type_in_roomName": "The user won't be able to type in {{roomName}}",
|
||||||
|
"The_user_will_be_able_to_type_in_roomName": "The user will be able to type in {{roomName}}",
|
||||||
|
"Enable_writing_in_room": "Enable writing in room",
|
||||||
|
"Disable_writing_in_room": "Disable writing in room",
|
||||||
"Pinned_a_message": "Pinned a message:"
|
"Pinned_a_message": "Pinned a message:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -753,5 +753,9 @@
|
||||||
"Call_started": "Chamada iniciada",
|
"Call_started": "Chamada iniciada",
|
||||||
"Supported_versions_expired_title": "{{workspace_name}} está executando uma versão não suportada do Rocket.Chat",
|
"Supported_versions_expired_title": "{{workspace_name}} está executando uma versão não suportada do Rocket.Chat",
|
||||||
"Supported_versions_expired_description": "Um administrador precisa atualizar o espaço de trabalho para uma versão suportada a fim de reabilitar o acesso a partir de aplicativos móveis e de desktop.",
|
"Supported_versions_expired_description": "Um administrador precisa atualizar o espaço de trabalho para uma versão suportada a fim de reabilitar o acesso a partir de aplicativos móveis e de desktop.",
|
||||||
"Supported_versions_warning_update_required": "Atualização necessária"
|
"Supported_versions_warning_update_required": "Atualização necessária",
|
||||||
|
"The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}",
|
||||||
|
"The_user_will_be_able_to_type_in_roomName": "O usuário poderá digitar em {{roomName}}",
|
||||||
|
"Enable_writing_in_room": "Permitir escrita na sala",
|
||||||
|
"Disable_writing_in_room": "Desabilitar escrita na sala"
|
||||||
}
|
}
|
|
@ -79,6 +79,8 @@ export default class Subscription extends Model {
|
||||||
|
|
||||||
@json('muted', sanitizer) muted;
|
@json('muted', sanitizer) muted;
|
||||||
|
|
||||||
|
@json('unmuted', sanitizer) unmuted;
|
||||||
|
|
||||||
@json('ignored', sanitizer) ignored;
|
@json('ignored', sanitizer) ignored;
|
||||||
|
|
||||||
@field('broadcast') broadcast;
|
@field('broadcast') broadcast;
|
||||||
|
|
|
@ -275,6 +275,15 @@ export default schemaMigrations({
|
||||||
columns: [{ name: 'sanitized_fname', type: 'string', isOptional: true }]
|
columns: [{ name: 'sanitized_fname', type: 'string', isOptional: true }]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toVersion: 23,
|
||||||
|
steps: [
|
||||||
|
addColumns({
|
||||||
|
table: 'subscriptions',
|
||||||
|
columns: [{ name: 'unmuted', type: 'string', isOptional: true }]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 22,
|
version: 23,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'subscriptions',
|
name: 'subscriptions',
|
||||||
|
@ -65,7 +65,8 @@ export default appSchema({
|
||||||
{ name: 'on_hold', type: 'boolean', isOptional: true },
|
{ name: 'on_hold', type: 'boolean', isOptional: true },
|
||||||
{ name: 'source', type: 'string', isOptional: true },
|
{ name: 'source', type: 'string', isOptional: true },
|
||||||
{ name: 'hide_mention_status', type: 'boolean', isOptional: true },
|
{ name: 'hide_mention_status', type: 'boolean', isOptional: true },
|
||||||
{ name: 'users_count', type: 'number', isOptional: true }
|
{ name: 'users_count', type: 'number', isOptional: true },
|
||||||
|
{ name: 'unmuted', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
|
|
@ -38,6 +38,7 @@ export default async (subscriptions: IServerSubscription[], rooms: IServerRoom[]
|
||||||
archived: s.archived,
|
archived: s.archived,
|
||||||
joinCodeRequired: s.joinCodeRequired,
|
joinCodeRequired: s.joinCodeRequired,
|
||||||
muted: s.muted,
|
muted: s.muted,
|
||||||
|
unmuted: s.unmuted,
|
||||||
broadcast: s.broadcast,
|
broadcast: s.broadcast,
|
||||||
prid: s.prid,
|
prid: s.prid,
|
||||||
draftMessage: s.draftMessage,
|
draftMessage: s.draftMessage,
|
||||||
|
@ -78,6 +79,7 @@ export default async (subscriptions: IServerSubscription[], rooms: IServerRoom[]
|
||||||
ro: r.ro,
|
ro: r.ro,
|
||||||
broadcast: r.broadcast,
|
broadcast: r.broadcast,
|
||||||
muted: r.muted,
|
muted: r.muted,
|
||||||
|
unmuted: r.unmuted,
|
||||||
sysMes: r.sysMes,
|
sysMes: r.sysMes,
|
||||||
v: r.v,
|
v: r.v,
|
||||||
departmentId: r.departmentId,
|
departmentId: r.departmentId,
|
||||||
|
|
|
@ -2,11 +2,13 @@ import { store as reduxStore } from '../../store/auxStore';
|
||||||
import { ISubscription } from '../../../definitions';
|
import { ISubscription } from '../../../definitions';
|
||||||
import { hasPermission } from './helpers';
|
import { hasPermission } from './helpers';
|
||||||
|
|
||||||
const canPostReadOnly = async ({ rid }: { rid: string }) => {
|
const canPostReadOnly = async (room: Partial<ISubscription>, username: string) => {
|
||||||
|
// RC 6.4.0
|
||||||
|
const isUnmuted = !!room?.unmuted?.find(m => m === username);
|
||||||
// TODO: this is not reactive. If this permission changes, the component won't be updated
|
// TODO: this is not reactive. If this permission changes, the component won't be updated
|
||||||
const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly'];
|
const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly'];
|
||||||
const permission = await hasPermission([postReadOnlyPermission], rid);
|
const permission = await hasPermission([postReadOnlyPermission], room.rid);
|
||||||
return permission[0];
|
return permission[0] || isUnmuted;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMuted = (room: Partial<ISubscription>, username: string) =>
|
const isMuted = (room: Partial<ISubscription>, username: string) =>
|
||||||
|
@ -20,7 +22,7 @@ export const isReadOnly = async (room: Partial<ISubscription>, username: string)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (room?.ro) {
|
if (room?.ro) {
|
||||||
const allowPost = await canPostReadOnly({ rid: room.rid as string });
|
const allowPost = await canPostReadOnly(room, username);
|
||||||
if (allowPost) {
|
if (allowPost) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,11 @@ export const merge = (
|
||||||
} else {
|
} else {
|
||||||
mergedSubscription.muted = [];
|
mergedSubscription.muted = [];
|
||||||
}
|
}
|
||||||
|
if (room?.unmuted?.length) {
|
||||||
|
mergedSubscription.unmuted = room.unmuted.filter(unmuted => !!unmuted);
|
||||||
|
} else {
|
||||||
|
mergedSubscription.unmuted = [];
|
||||||
|
}
|
||||||
if (room?.v) {
|
if (room?.v) {
|
||||||
mergedSubscription.visitor = room.v;
|
mergedSubscription.visitor = room.v;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ const createOrUpdateSubscription = async (subscription: ISubscription, room: ISe
|
||||||
archived: s.archived,
|
archived: s.archived,
|
||||||
joinCodeRequired: s.joinCodeRequired,
|
joinCodeRequired: s.joinCodeRequired,
|
||||||
muted: s.muted,
|
muted: s.muted,
|
||||||
|
unmuted: s.unmuted,
|
||||||
ignored: s.ignored,
|
ignored: s.ignored,
|
||||||
broadcast: s.broadcast,
|
broadcast: s.broadcast,
|
||||||
prid: s.prid,
|
prid: s.prid,
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, up
|
||||||
|
|
||||||
export const handleMute = async (user: TUserModel, rid: string) => {
|
export const handleMute = async (user: TUserModel, rid: string) => {
|
||||||
try {
|
try {
|
||||||
await Services.toggleMuteUserInRoom(rid, user?.username, !user?.muted);
|
await Services.toggleMuteUserInRoom(rid, user?.username, !user.muted);
|
||||||
EventEmitter.emit(LISTENER, {
|
EventEmitter.emit(LISTENER, {
|
||||||
message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') })
|
message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') })
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { NavigationProp, RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
import { NavigationProp, RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||||
import React, { useEffect, useReducer } from 'react';
|
import React, { useEffect, useReducer } from 'react';
|
||||||
import { FlatList, Text, View } from 'react-native';
|
import { FlatList, Text, View } from 'react-native';
|
||||||
|
import { shallowEqual } from 'react-redux';
|
||||||
|
|
||||||
import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet';
|
import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet';
|
||||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
import { CustomIcon } from '../../containers/CustomIcon';
|
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import * as List from '../../containers/List';
|
import * as List from '../../containers/List';
|
||||||
import { RadioButton } from '../../containers/RadioButton';
|
import { RadioButton } from '../../containers/RadioButton';
|
||||||
|
@ -15,7 +16,7 @@ import UserItem from '../../containers/UserItem';
|
||||||
import { TSubscriptionModel, TUserModel } from '../../definitions';
|
import { TSubscriptionModel, TUserModel } from '../../definitions';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { useAppSelector, usePermissions } from '../../lib/hooks';
|
import { useAppSelector, usePermissions } from '../../lib/hooks';
|
||||||
import { getRoomTitle, isGroupChat } from '../../lib/methods/helpers';
|
import { compareServerVersion, getRoomTitle, isGroupChat } from '../../lib/methods/helpers';
|
||||||
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
|
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
|
||||||
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
||||||
import log from '../../lib/methods/helpers/log';
|
import log from '../../lib/methods/helpers/log';
|
||||||
|
@ -73,10 +74,15 @@ const RoomMembersView = (): React.ReactElement => {
|
||||||
const { params } = useRoute<RouteProp<ModalStackParamList, 'RoomMembersView'>>();
|
const { params } = useRoute<RouteProp<ModalStackParamList, 'RoomMembersView'>>();
|
||||||
const navigation = useNavigation<NavigationProp<ModalStackParamList, 'RoomMembersView'>>();
|
const navigation = useNavigation<NavigationProp<ModalStackParamList, 'RoomMembersView'>>();
|
||||||
|
|
||||||
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
|
const { isMasterDetail, serverVersion, useRealName, user } = useAppSelector(
|
||||||
|
state => ({
|
||||||
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
const user = useAppSelector(state => getUserSelector(state));
|
useRealName: state.settings.UI_Use_Real_Name,
|
||||||
|
user: getUserSelector(state),
|
||||||
|
serverVersion: state.server.version
|
||||||
|
}),
|
||||||
|
shallowEqual
|
||||||
|
);
|
||||||
|
|
||||||
const [state, updateState] = useReducer(
|
const [state, updateState] = useReducer(
|
||||||
(state: IRoomMembersViewState, newState: Partial<IRoomMembersViewState>) => ({ ...state, ...newState }),
|
(state: IRoomMembersViewState, newState: Partial<IRoomMembersViewState>) => ({ ...state, ...newState }),
|
||||||
|
@ -200,38 +206,6 @@ const RoomMembersView = (): React.ReactElement => {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Ignore
|
|
||||||
if (selectedUser._id !== user.id) {
|
|
||||||
const { ignored } = room;
|
|
||||||
const isIgnored = ignored?.includes?.(selectedUser._id);
|
|
||||||
options.push({
|
|
||||||
icon: 'ignore',
|
|
||||||
title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
|
|
||||||
onPress: () => handleIgnore(selectedUser._id, !isIgnored, room.rid),
|
|
||||||
testID: 'action-sheet-ignore-user'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (muteUserPermission) {
|
|
||||||
const { muted = [] } = room;
|
|
||||||
const userIsMuted = muted.find?.(m => m === selectedUser.username);
|
|
||||||
selectedUser.muted = !!userIsMuted;
|
|
||||||
options.push({
|
|
||||||
icon: userIsMuted ? 'audio' : 'audio-disabled',
|
|
||||||
title: I18n.t(userIsMuted ? 'Unmute' : 'Mute'),
|
|
||||||
onPress: () => {
|
|
||||||
showConfirmationAlert({
|
|
||||||
message: I18n.t(`The_user_${userIsMuted ? 'will' : 'wont'}_be_able_to_type_in_roomName`, {
|
|
||||||
roomName: getRoomTitle(room)
|
|
||||||
}),
|
|
||||||
confirmationText: I18n.t(userIsMuted ? 'Unmute' : 'Mute'),
|
|
||||||
onPress: () => handleMute(selectedUser, room.rid)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
testID: 'action-sheet-mute-user'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Owner
|
// Owner
|
||||||
if (setOwnerPermission) {
|
if (setOwnerPermission) {
|
||||||
const isOwner = fetchRole('owner', selectedUser, roomRoles);
|
const isOwner = fetchRole('owner', selectedUser, roomRoles);
|
||||||
|
@ -277,6 +251,47 @@ const RoomMembersView = (): React.ReactElement => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (muteUserPermission) {
|
||||||
|
const { muted = [], ro: readOnly, unmuted = [] } = room;
|
||||||
|
let userIsMuted = !!muted.find?.(m => m === selectedUser.username);
|
||||||
|
let icon: TIconsName = userIsMuted ? 'audio' : 'audio-disabled';
|
||||||
|
let title = I18n.t(userIsMuted ? 'Unmute' : 'Mute');
|
||||||
|
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.4.0')) {
|
||||||
|
if (readOnly) {
|
||||||
|
userIsMuted = !unmuted?.find?.(m => m === selectedUser.username);
|
||||||
|
}
|
||||||
|
icon = userIsMuted ? 'message' : 'message-disabled';
|
||||||
|
title = I18n.t(userIsMuted ? 'Enable_writing_in_room' : 'Disable_writing_in_room');
|
||||||
|
}
|
||||||
|
selectedUser.muted = !!userIsMuted;
|
||||||
|
options.push({
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
onPress: () => {
|
||||||
|
showConfirmationAlert({
|
||||||
|
message: I18n.t(`The_user_${userIsMuted ? 'will' : 'wont'}_be_able_to_type_in_roomName`, {
|
||||||
|
roomName: getRoomTitle(room)
|
||||||
|
}),
|
||||||
|
confirmationText: title,
|
||||||
|
onPress: () => handleMute(selectedUser, room.rid)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
testID: 'action-sheet-mute-user'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore
|
||||||
|
if (selectedUser._id !== user.id) {
|
||||||
|
const { ignored } = room;
|
||||||
|
const isIgnored = ignored?.includes?.(selectedUser._id);
|
||||||
|
options.push({
|
||||||
|
icon: 'ignore',
|
||||||
|
title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
|
||||||
|
onPress: () => handleIgnore(selectedUser._id, !isIgnored, room.rid),
|
||||||
|
testID: 'action-sheet-ignore-user'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Remove from team
|
// Remove from team
|
||||||
if (editTeamMemberPermission) {
|
if (editTeamMemberPermission) {
|
||||||
options.push({
|
options.push({
|
||||||
|
|
|
@ -140,7 +140,8 @@ const roomAttrsUpdate = [
|
||||||
'onHold',
|
'onHold',
|
||||||
't',
|
't',
|
||||||
'autoTranslate',
|
'autoTranslate',
|
||||||
'autoTranslateLanguage'
|
'autoTranslateLanguage',
|
||||||
|
'unmuted'
|
||||||
] as TRoomUpdate[];
|
] as TRoomUpdate[];
|
||||||
|
|
||||||
interface IRoomViewProps extends IActionSheetProvider, IBaseScreen<ChatsStackParamList, 'RoomView'> {
|
interface IRoomViewProps extends IActionSheetProvider, IBaseScreen<ChatsStackParamList, 'RoomView'> {
|
||||||
|
|
Loading…
Reference in New Issue