diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts index d49eccf67..ddafdad58 100644 --- a/app/definitions/IRoom.ts +++ b/app/definitions/IRoom.ts @@ -39,6 +39,7 @@ export interface IRoom { default?: boolean; featured?: boolean; muted?: string[]; + unmuted?: string[]; teamId?: string; ignored?: string; diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index a5a044e6a..72fd742ba 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -72,6 +72,7 @@ export interface ISubscription { archived: boolean; joinCodeRequired?: boolean; muted?: string[]; + unmuted?: string[]; ignored?: string[]; broadcast?: boolean; prid?: string; @@ -111,9 +112,10 @@ export interface ISubscription { uploads: RelationModified; } -export type TSubscriptionModel = ISubscription & Model & { - asPlain: () => ISubscription; -}; +export type TSubscriptionModel = ISubscription & + Model & { + asPlain: () => ISubscription; + }; export type TSubscription = TSubscriptionModel | ISubscription; // https://github.com/RocketChat/Rocket.Chat/blob/a88a96fcadd925b678ff27ada37075e029f78b5e/definition/ISubscription.ts#L8 diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index f33210196..d7cdb3034 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -754,5 +754,9 @@ "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_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:" } diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 5c709997c..6a89d015e 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -753,5 +753,9 @@ "Call_started": "Chamada iniciada", "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_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" } \ No newline at end of file diff --git a/app/lib/database/model/Subscription.js b/app/lib/database/model/Subscription.js index 2675a6f52..59ade5c9e 100644 --- a/app/lib/database/model/Subscription.js +++ b/app/lib/database/model/Subscription.js @@ -79,6 +79,8 @@ export default class Subscription extends Model { @json('muted', sanitizer) muted; + @json('unmuted', sanitizer) unmuted; + @json('ignored', sanitizer) ignored; @field('broadcast') broadcast; diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js index 71fc4a759..08a111178 100644 --- a/app/lib/database/model/migrations.js +++ b/app/lib/database/model/migrations.js @@ -275,6 +275,15 @@ export default schemaMigrations({ columns: [{ name: 'sanitized_fname', type: 'string', isOptional: true }] }) ] + }, + { + toVersion: 23, + steps: [ + addColumns({ + table: 'subscriptions', + columns: [{ name: 'unmuted', type: 'string', isOptional: true }] + }) + ] } ] }); diff --git a/app/lib/database/schema/app.js b/app/lib/database/schema/app.js index 5e408ade0..5ae48d6df 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: 22, + version: 23, tables: [ tableSchema({ name: 'subscriptions', @@ -65,7 +65,8 @@ export default appSchema({ { name: 'on_hold', type: 'boolean', isOptional: true }, { name: 'source', type: 'string', 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({ diff --git a/app/lib/methods/helpers/findSubscriptionsRooms.ts b/app/lib/methods/helpers/findSubscriptionsRooms.ts index e7df6c5b1..325ff3c4e 100644 --- a/app/lib/methods/helpers/findSubscriptionsRooms.ts +++ b/app/lib/methods/helpers/findSubscriptionsRooms.ts @@ -38,6 +38,7 @@ export default async (subscriptions: IServerSubscription[], rooms: IServerRoom[] archived: s.archived, joinCodeRequired: s.joinCodeRequired, muted: s.muted, + unmuted: s.unmuted, broadcast: s.broadcast, prid: s.prid, draftMessage: s.draftMessage, @@ -78,6 +79,7 @@ export default async (subscriptions: IServerSubscription[], rooms: IServerRoom[] ro: r.ro, broadcast: r.broadcast, muted: r.muted, + unmuted: r.unmuted, sysMes: r.sysMes, v: r.v, departmentId: r.departmentId, diff --git a/app/lib/methods/helpers/isReadOnly.ts b/app/lib/methods/helpers/isReadOnly.ts index 81977ab70..ecb937a2a 100644 --- a/app/lib/methods/helpers/isReadOnly.ts +++ b/app/lib/methods/helpers/isReadOnly.ts @@ -2,11 +2,13 @@ import { store as reduxStore } from '../../store/auxStore'; import { ISubscription } from '../../../definitions'; import { hasPermission } from './helpers'; -const canPostReadOnly = async ({ rid }: { rid: string }) => { +const canPostReadOnly = async (room: Partial, 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 const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly']; - const permission = await hasPermission([postReadOnlyPermission], rid); - return permission[0]; + const permission = await hasPermission([postReadOnlyPermission], room.rid); + return permission[0] || isUnmuted; }; const isMuted = (room: Partial, username: string) => @@ -20,7 +22,7 @@ export const isReadOnly = async (room: Partial, username: string) return true; } if (room?.ro) { - const allowPost = await canPostReadOnly({ rid: room.rid as string }); + const allowPost = await canPostReadOnly(room, username); if (allowPost) { return false; } diff --git a/app/lib/methods/helpers/mergeSubscriptionsRooms.ts b/app/lib/methods/helpers/mergeSubscriptionsRooms.ts index fec41dfd3..9f9feb64a 100644 --- a/app/lib/methods/helpers/mergeSubscriptionsRooms.ts +++ b/app/lib/methods/helpers/mergeSubscriptionsRooms.ts @@ -67,6 +67,11 @@ export const merge = ( } else { mergedSubscription.muted = []; } + if (room?.unmuted?.length) { + mergedSubscription.unmuted = room.unmuted.filter(unmuted => !!unmuted); + } else { + mergedSubscription.unmuted = []; + } if (room?.v) { mergedSubscription.visitor = room.v; } diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 8a9703c9b..dd3cf6514 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -83,6 +83,7 @@ const createOrUpdateSubscription = async (subscription: ISubscription, room: ISe archived: s.archived, joinCodeRequired: s.joinCodeRequired, muted: s.muted, + unmuted: s.unmuted, ignored: s.ignored, broadcast: s.broadcast, prid: s.prid, diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts index d1a7e8945..269d26f4f 100644 --- a/app/views/RoomMembersView/helpers.ts +++ b/app/views/RoomMembersView/helpers.ts @@ -37,7 +37,7 @@ export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, up export const handleMute = async (user: TUserModel, rid: string) => { try { - await Services.toggleMuteUserInRoom(rid, user?.username, !user?.muted); + await Services.toggleMuteUserInRoom(rid, user?.username, !user.muted); EventEmitter.emit(LISTENER, { message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') }) }); diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 24f652a91..2babb7f8b 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -1,10 +1,11 @@ import { NavigationProp, RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import React, { useEffect, useReducer } from 'react'; import { FlatList, Text, View } from 'react-native'; +import { shallowEqual } from 'react-redux'; import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet'; import ActivityIndicator from '../../containers/ActivityIndicator'; -import { CustomIcon } from '../../containers/CustomIcon'; +import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import * as HeaderButton from '../../containers/HeaderButton'; import * as List from '../../containers/List'; import { RadioButton } from '../../containers/RadioButton'; @@ -15,7 +16,7 @@ import UserItem from '../../containers/UserItem'; import { TSubscriptionModel, TUserModel } from '../../definitions'; import I18n from '../../i18n'; 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 { showConfirmationAlert } from '../../lib/methods/helpers/info'; import log from '../../lib/methods/helpers/log'; @@ -73,10 +74,15 @@ const RoomMembersView = (): React.ReactElement => { const { params } = useRoute>(); const navigation = useNavigation>(); - const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); - - const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name); - const user = useAppSelector(state => getUserSelector(state)); + const { isMasterDetail, serverVersion, useRealName, user } = useAppSelector( + state => ({ + isMasterDetail: state.app.isMasterDetail, + useRealName: state.settings.UI_Use_Real_Name, + user: getUserSelector(state), + serverVersion: state.server.version + }), + shallowEqual + ); const [state, updateState] = useReducer( (state: IRoomMembersViewState, newState: Partial) => ({ ...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 if (setOwnerPermission) { 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 if (editTeamMemberPermission) { options.push({ diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 075e02b38..2f733f75d 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -140,7 +140,8 @@ const roomAttrsUpdate = [ 'onHold', 't', 'autoTranslate', - 'autoTranslateLanguage' + 'autoTranslateLanguage', + 'unmuted' ] as TRoomUpdate[]; interface IRoomViewProps extends IActionSheetProvider, IBaseScreen {