diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx index e9aa0d71fb8..a936bbe81ad 100644 --- a/app/components/post_draft/send_handler/send_handler.tsx +++ b/app/components/post_draft/send_handler/send_handler.tsx @@ -166,7 +166,7 @@ export default function SendHandler({ const sendCommand = useCallback(async () => { if (value.trim().startsWith('/call')) { - const {handled, error} = await handleCallsSlashCommand(value.trim(), serverUrl, channelId, rootId, currentUserId, intl); + const {handled, error} = await handleCallsSlashCommand(value.trim(), serverUrl, channelId, channelType ?? '', rootId, currentUserId, intl); if (handled) { setSendingMessage(false); clearDraft(); diff --git a/app/products/calls/actions/calls.ts b/app/products/calls/actions/calls.ts index 1b851f87e20..2c7d6b2947a 100644 --- a/app/products/calls/actions/calls.ts +++ b/app/products/calls/actions/calls.ts @@ -30,6 +30,7 @@ import { setSpeakerPhone, } from '@calls/state'; import {type AudioDevice, type Call, type CallSession, type CallsConnection, EndCallReturn} from '@calls/types/calls'; +import {areGroupCallsAllowed} from '@calls/utils'; import {General, Preferences} from '@constants'; import Calls from '@constants/calls'; import DatabaseManager from '@database/manager'; @@ -493,7 +494,7 @@ export const dismissIncomingCall = async (serverUrl: string, channelId: string) }; // handleCallsSlashCommand will return true if the slash command was handled -export const handleCallsSlashCommand = async (value: string, serverUrl: string, channelId: string, rootId: string, currentUserId: string, intl: IntlShape): +export const handleCallsSlashCommand = async (value: string, serverUrl: string, channelId: string, channelType: string, rootId: string, currentUserId: string, intl: IntlShape): Promise<{ handled?: boolean; error?: string }> => { const tokens = value.split(' '); if (tokens.length < 2 || tokens[0] !== '/call') { @@ -505,6 +506,15 @@ export const handleCallsSlashCommand = async (value: string, serverUrl: string, await handleEndCall(serverUrl, channelId, currentUserId, intl); return {handled: true}; case 'start': { + if (!areGroupCallsAllowed(getCallsConfig(serverUrl)) && channelType !== General.DM_CHANNEL) { + return { + error: intl.formatMessage({ + id: 'mobile.calls_group_calls_not_available', + defaultMessage: 'Calls are only available in DM channels.', + }), + }; + } + if (getChannelsWithCalls(serverUrl)[channelId]) { return { error: intl.formatMessage({ @@ -518,6 +528,15 @@ export const handleCallsSlashCommand = async (value: string, serverUrl: string, return {handled: true}; } case 'join': { + if (!areGroupCallsAllowed(getCallsConfig(serverUrl)) && channelType !== General.DM_CHANNEL) { + return { + error: intl.formatMessage({ + id: 'mobile.calls_group_calls_not_available', + defaultMessage: 'Calls are only available in DM channels.', + }), + }; + } + const title = tokens.length > 2 ? tokens.slice(2).join(' ') : undefined; await leaveAndJoinWithAlert(intl, serverUrl, channelId, title, rootId); return {handled: true}; diff --git a/app/products/calls/types/calls.ts b/app/products/calls/types/calls.ts index dc2da1f8918..c1d5111fa25 100644 --- a/app/products/calls/types/calls.ts +++ b/app/products/calls/types/calls.ts @@ -177,6 +177,7 @@ export const DefaultCallsConfig: CallsConfigState = { HostControlsAllowed: false, EnableAV1: false, TranscribeAPI: TranscribeAPI.WhisperCPP, + GroupCallsAllowed: true, // Set to true to keep backward compatibility with older servers. }; export type ApiResp = { diff --git a/app/products/calls/utils.ts b/app/products/calls/utils.ts index 7f8b162afa9..086df8ec3cf 100644 --- a/app/products/calls/utils.ts +++ b/app/products/calls/utils.ts @@ -108,6 +108,10 @@ export function isHostControlsAllowed(config: CallsConfigState) { return Boolean(config.HostControlsAllowed); } +export function areGroupCallsAllowed(config: CallsConfigState) { + return Boolean(config.GroupCallsAllowed); +} + export function isCallsCustomMessage(post: PostModel | Post): boolean { return Boolean(post.type && post.type === Post.POST_TYPES.CUSTOM_CALLS); } diff --git a/app/screens/channel/channel.tsx b/app/screens/channel/channel.tsx index 55e07186452..845d20e6bbf 100644 --- a/app/screens/channel/channel.tsx +++ b/app/screens/channel/channel.tsx @@ -34,6 +34,7 @@ type ChannelProps = { showJoinCallBanner: boolean; isInACall: boolean; isCallsEnabledInChannel: boolean; + groupCallsAllowed: boolean; showIncomingCalls: boolean; isTabletView?: boolean; dismissedGMasDMNotice: PreferenceModel[]; @@ -58,6 +59,7 @@ const Channel = ({ showJoinCallBanner, isInACall, isCallsEnabledInChannel, + groupCallsAllowed, showIncomingCalls, isTabletView, dismissedGMasDMNotice, @@ -125,6 +127,7 @@ const Channel = ({ channelId={channelId} componentId={componentId} callsEnabledInChannel={isCallsEnabledInChannel} + groupCallsAllowed={groupCallsAllowed} isTabletView={isTabletView} shouldRenderBookmarks={shouldRender} /> diff --git a/app/screens/channel/header/header.tsx b/app/screens/channel/header/header.tsx index 27b58498053..d9ea6981eec 100644 --- a/app/screens/channel/header/header.tsx +++ b/app/screens/channel/header/header.tsx @@ -46,6 +46,7 @@ type ChannelProps = { searchTerm: string; teamId: string; callsEnabledInChannel: boolean; + groupCallsAllowed: boolean; isTabletView?: boolean; shouldRenderBookmarks: boolean; }; @@ -78,7 +79,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const ChannelHeader = ({ canAddBookmarks, channelId, channelType, componentId, customStatus, displayName, hasBookmarks, isBookmarksEnabled, isCustomStatusEnabled, isCustomStatusExpired, isOwnDirectMessage, memberCount, - searchTerm, teamId, callsEnabledInChannel, isTabletView, shouldRenderBookmarks, + searchTerm, teamId, callsEnabledInChannel, groupCallsAllowed, isTabletView, shouldRenderBookmarks, }: ChannelProps) => { const intl = useIntl(); const isTablet = useIsTablet(); @@ -89,7 +90,10 @@ const ChannelHeader = ({ // NOTE: callsEnabledInChannel will be true/false (not undefined) based on explicit state + the DefaultEnabled system setting // which ultimately comes from channel/index.tsx, and observeIsCallsEnabledInChannel - const callsAvailable = callsEnabledInChannel; + let callsAvailable = callsEnabledInChannel; + if (!groupCallsAllowed && channelType !== General.DM_CHANNEL) { + callsAvailable = false; + } const isDMorGM = isTypeDMorGM(channelType); const contextStyle = useMemo(() => ({ diff --git a/app/screens/channel/index.tsx b/app/screens/channel/index.tsx index 049452fde9e..f7b20dc1d99 100644 --- a/app/screens/channel/index.tsx +++ b/app/screens/channel/index.tsx @@ -5,6 +5,7 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import {combineLatestWith, distinctUntilChanged, of as of$, switchMap} from 'rxjs'; import {observeCallStateInChannel, observeIsCallsEnabledInChannel} from '@calls/observers'; +import {observeCallsConfig} from '@calls/state'; import {Preferences} from '@constants'; import {withServerUrl} from '@context/server'; import {observeCurrentChannel} from '@queries/servers/channel'; @@ -43,10 +44,16 @@ const enhanced = withObservables([], ({database, serverUrl}: EnhanceProps) => { }), ); + const groupCallsAllowed = observeCallsConfig(serverUrl).pipe( + switchMap((config) => of$(config.GroupCallsAllowed)), + distinctUntilChanged(), + ); + return { channelId, ...observeCallStateInChannel(serverUrl, database, channelId), isCallsEnabledInChannel: observeIsCallsEnabledInChannel(database, serverUrl, channelId), + groupCallsAllowed, dismissedGMasDMNotice, channelType, currentUserId, diff --git a/app/screens/channel_info/channel_info.tsx b/app/screens/channel_info/channel_info.tsx index 40d8214119d..060ebbec5f4 100644 --- a/app/screens/channel_info/channel_info.tsx +++ b/app/screens/channel_info/channel_info.tsx @@ -28,13 +28,14 @@ import type {AvailableScreens} from '@typings/screens/navigation'; type Props = { canAddBookmarks: boolean; canEnableDisableCalls: boolean; - canManageMembers: boolean; canManageSettings: boolean; channelId: string; closeButtonId: string; componentId: AvailableScreens; isBookmarksEnabled: boolean; isCallsEnabledInChannel: boolean; + groupCallsAllowed: boolean; + canManageMembers: boolean; isConvertGMFeatureAvailable: boolean; isCRTEnabled: boolean; isGuestUser: boolean; @@ -68,6 +69,7 @@ const ChannelInfo = ({ componentId, isBookmarksEnabled, isCallsEnabledInChannel, + groupCallsAllowed, isConvertGMFeatureAvailable, isCRTEnabled, isGuestUser, @@ -79,7 +81,10 @@ const ChannelInfo = ({ // NOTE: isCallsEnabledInChannel will be true/false (not undefined) based on explicit state + the DefaultEnabled system setting // which comes from observeIsCallsEnabledInChannel - const callsAvailable = isCallsEnabledInChannel; + let callsAvailable = isCallsEnabledInChannel; + if (!groupCallsAllowed && type !== General.DM_CHANNEL) { + callsAvailable = false; + } const onPressed = useCallback(() => { return dismissModal({componentId}); diff --git a/app/screens/channel_info/index.ts b/app/screens/channel_info/index.ts index e6b2c2761c9..da0b1a64ba1 100644 --- a/app/screens/channel_info/index.ts +++ b/app/screens/channel_info/index.ts @@ -102,6 +102,10 @@ const enhanced = withObservables([], ({serverUrl, database}: Props) => { }), ); const isCallsEnabledInChannel = observeIsCallsEnabledInChannel(database, serverUrl, observeCurrentChannelId(database)); + const groupCallsAllowed = observeCallsConfig(serverUrl).pipe( + switchMap((config) => of$(config.GroupCallsAllowed)), + distinctUntilChanged(), + ); const canManageMembers = currentUser.pipe( combineLatestWith(channelId), @@ -135,11 +139,12 @@ const enhanced = withObservables([], ({serverUrl, database}: Props) => { return { type, canEnableDisableCalls, + isCallsEnabledInChannel, + groupCallsAllowed, canAddBookmarks, canManageMembers, canManageSettings, isBookmarksEnabled, - isCallsEnabledInChannel, isCRTEnabled: observeIsCRTEnabled(database), isGuestUser, isConvertGMFeatureAvailable, diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 3705d2bd090..e03c2695e82 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -486,6 +486,7 @@ "mobile.calls_ended_at": "Ended at", "mobile.calls_error_message": "Error: {error}", "mobile.calls_error_title": "Error", + "mobile.calls_group_calls_not_available": "Calls are only available in DM channels.", "mobile.calls_headset": "Headset", "mobile.calls_hide_cc": "Hide live captions", "mobile.calls_host": "host", diff --git a/package-lock.json b/package-lock.json index dc8da8df33d..f6c549742d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@formatjs/intl-numberformat": "8.10.3", "@formatjs/intl-pluralrules": "5.2.14", "@gorhom/bottom-sheet": "4.6.4", - "@mattermost/calls": "github:mattermost/calls-common#5cc5598fe9574125a61a3b54501deb6b65fcc70f", + "@mattermost/calls": "github:mattermost/calls-common#1ce6defb1ee0c1e0f106ddff8f46c37d10d60b76", "@mattermost/compass-icons": "0.1.45", "@mattermost/hardware-keyboard": "file:./libraries/@mattermost/hardware-keyboard", "@mattermost/keyboard-tracker": "file:./libraries/@mattermost/keyboard-tracker", @@ -5860,8 +5860,8 @@ "node_modules/@mattermost/calls": { "name": "@mattermost/calls-common", "version": "0.27.2", - "resolved": "git+ssh://git@github.com/mattermost/calls-common.git#5cc5598fe9574125a61a3b54501deb6b65fcc70f", - "integrity": "sha512-oJkw51naheYGDlxV4iRx0rnI1zAjWE/InRG6QXPvq2nbJ9U0QUPsk/H9/O6trP2ZuXR/v1u72z/OwqLud1blbQ==" + "resolved": "git+ssh://git@github.com/mattermost/calls-common.git#1ce6defb1ee0c1e0f106ddff8f46c37d10d60b76", + "integrity": "sha512-VeX0GT1g8bl7AqG5TJEnbkTTVbc+CqZttpPBKYDkMJd86CGjtURc4o83OUu3TNsEI3opSpJfbetScUTG9TWNLw==" }, "node_modules/@mattermost/commonmark": { "version": "0.30.1-2", diff --git a/package.json b/package.json index 2c31d2015d1..c587d91d24f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@formatjs/intl-numberformat": "8.10.3", "@formatjs/intl-pluralrules": "5.2.14", "@gorhom/bottom-sheet": "4.6.4", - "@mattermost/calls": "github:mattermost/calls-common#5cc5598fe9574125a61a3b54501deb6b65fcc70f", + "@mattermost/calls": "github:mattermost/calls-common#1ce6defb1ee0c1e0f106ddff8f46c37d10d60b76", "@mattermost/compass-icons": "0.1.45", "@mattermost/hardware-keyboard": "file:./libraries/@mattermost/hardware-keyboard", "@mattermost/keyboard-tracker": "file:./libraries/@mattermost/keyboard-tracker",