diff --git a/app/lib/constants/defaultSettings.ts b/app/lib/constants/defaultSettings.ts index 333e580ef..080c9cac6 100644 --- a/app/lib/constants/defaultSettings.ts +++ b/app/lib/constants/defaultSettings.ts @@ -1,4 +1,31 @@ -// 🚨🚨 48 settings after login. Pay attention not to reach 50 as that's the limit per request. +// DEPRECATED: This settings are deprecated and will be removed in the LTS only support +const deprecatedSettings = { + Jitsi_Enable_Teams: { + type: 'valueAsBoolean' + }, + Jitsi_Enable_Channels: { + type: 'valuesAsBoolean' + }, + Jitsi_Enabled: { + type: 'valueAsBoolean' + }, + Jitsi_SSL: { + type: 'valueAsBoolean' + }, + Jitsi_Domain: { + type: 'valueAsString' + }, + Jitsi_Enabled_TokenAuth: { + type: 'valueAsBoolean' + }, + Jitsi_URL_Room_Hash: { + type: 'valueAsBoolean' + }, + Jitsi_URL_Room_Prefix: { + type: 'valueAsString' + } +}; + export const defaultSettings = { Accounts_AllowEmailChange: { type: 'valueAsBoolean' @@ -90,24 +117,6 @@ export const defaultSettings = { Livechat_request_comment_when_closing_conversation: { type: 'valueAsBoolean' }, - Jitsi_Enabled: { - type: 'valueAsBoolean' - }, - Jitsi_SSL: { - type: 'valueAsBoolean' - }, - Jitsi_Domain: { - type: 'valueAsString' - }, - Jitsi_Enabled_TokenAuth: { - type: 'valueAsBoolean' - }, - Jitsi_URL_Room_Hash: { - type: 'valueAsBoolean' - }, - Jitsi_URL_Room_Prefix: { - type: 'valueAsString' - }, Message_AllowDeleting: { type: 'valueAsBoolean' }, @@ -198,12 +207,6 @@ export const defaultSettings = { Accounts_AllowInvisibleStatusOption: { type: 'valueAsString' }, - Jitsi_Enable_Teams: { - type: 'valueAsBoolean' - }, - Jitsi_Enable_Channels: { - type: 'valuesAsBoolean' - }, Canned_Responses_Enable: { type: 'valueAsBoolean' }, @@ -236,5 +239,9 @@ export const defaultSettings = { }, Presence_broadcast_disabled: { type: 'valueAsBoolean' - } + }, + Omnichannel_call_provider: { + type: 'valueAsBoolean' + }, + ...deprecatedSettings } as const; diff --git a/app/lib/hooks/useSetting.ts b/app/lib/hooks/useSetting.ts new file mode 100644 index 000000000..523da35a8 --- /dev/null +++ b/app/lib/hooks/useSetting.ts @@ -0,0 +1,6 @@ +import { TSettingsValues, TSupportedSettings } from '../../reducers/settings'; +import { useAppSelector } from './useAppSelector'; + +export function useSetting(key: TSupportedSettings): TSettingsValues { + return useAppSelector(state => state.settings[key]) as TSettingsValues; +} diff --git a/app/lib/hooks/useVideoConf/index.tsx b/app/lib/hooks/useVideoConf/index.tsx index e7e80b216..47e6b7ff6 100644 --- a/app/lib/hooks/useVideoConf/index.tsx +++ b/app/lib/hooks/useVideoConf/index.tsx @@ -1,17 +1,16 @@ import { Camera } from 'expo-camera'; -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { useActionSheet } from '../../../containers/ActionSheet'; -import { SubscriptionType } from '../../../definitions'; import i18n from '../../../i18n'; import { getUserSelector } from '../../../selectors/login'; -import { getSubscriptionByRoomId } from '../../database/services/Subscription'; import { compareServerVersion, showErrorAlert } from '../../methods/helpers'; import { handleAndroidBltPermission } from '../../methods/videoConf'; import { Services } from '../../services'; import { useAppSelector } from '../useAppSelector'; import { useSnaps } from '../useSnaps'; import StartACallActionSheet from './StartACallActionSheet'; +import { useVideoConfCall } from './useVideoConfCall'; const availabilityErrors = { NOT_CONFIGURED: 'video-conf-provider-not-configured', @@ -24,58 +23,44 @@ const handleErrors = (isAdmin: boolean, error: typeof availabilityErrors[keyof t return showErrorAlert(i18n.t(`${error}-body`), i18n.t(`${error}-header`)); }; -export const useVideoConf = (rid: string): { showInitCallActionSheet: () => Promise; showCallOption: boolean } => { - const [showCallOption, setShowCallOption] = useState(false); - - const serverVersion = useAppSelector(state => state.server.version); - const jitsiEnabled = useAppSelector(state => state.settings.Jitsi_Enabled); - const jitsiEnableTeams = useAppSelector(state => state.settings.Jitsi_Enable_Teams); - const jitsiEnableChannels = useAppSelector(state => state.settings.Jitsi_Enable_Channels); +export const useVideoConf = ( + rid: string +): { showInitCallActionSheet: () => Promise; callEnabled: boolean; disabledTooltip?: boolean } => { const user = useAppSelector(state => getUserSelector(state)); + const serverVersion = useAppSelector(state => state.server.version); + + const { callEnabled, disabledTooltip } = useVideoConfCall(rid); const [permission, requestPermission] = Camera.useCameraPermissions(); - const isServer5OrNewer = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0'); - const { showActionSheet } = useActionSheet(); const snaps = useSnaps(404); - const handleShowCallOption = async () => { - if (isServer5OrNewer) return setShowCallOption(true); - const room = await getSubscriptionByRoomId(rid); - - if (room) { - const isJitsiDisabledForTeams = room.teamMain && !jitsiEnableTeams; - const isJitsiDisabledForChannels = !room.teamMain && (room.t === 'p' || room.t === 'c') && !jitsiEnableChannels; - - if (room.t === SubscriptionType.DIRECT) return setShowCallOption(!!jitsiEnabled); - if (room.t === SubscriptionType.CHANNEL) return setShowCallOption(!isJitsiDisabledForChannels); - if (room.t === SubscriptionType.GROUP) return setShowCallOption(!isJitsiDisabledForTeams); - } - - return setShowCallOption(false); - }; + const isServer5OrNewer = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0'); const canInitAnCall = async () => { - if (isServer5OrNewer) { - try { - await Services.videoConferenceGetCapabilities(); - return true; - } catch (error: any) { - const isAdmin = !!['admin'].find(role => user.roles?.includes(role)); - switch (error?.error) { - case availabilityErrors.NOT_CONFIGURED: - return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED); - case availabilityErrors.NOT_ACTIVE: - return handleErrors(isAdmin, availabilityErrors.NOT_ACTIVE); - case availabilityErrors.NO_APP: - return handleErrors(isAdmin, availabilityErrors.NO_APP); - default: - return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED); + if (callEnabled) { + if (isServer5OrNewer) { + try { + await Services.videoConferenceGetCapabilities(); + return true; + } catch (error: any) { + const isAdmin = !!user.roles?.includes('admin'); + switch (error?.error) { + case availabilityErrors.NOT_CONFIGURED: + return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED); + case availabilityErrors.NOT_ACTIVE: + return handleErrors(isAdmin, availabilityErrors.NOT_ACTIVE); + case availabilityErrors.NO_APP: + return handleErrors(isAdmin, availabilityErrors.NO_APP); + default: + return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED); + } } } + return true; } - return true; + return false; }; const showInitCallActionSheet = async () => { @@ -92,9 +77,5 @@ export const useVideoConf = (rid: string): { showInitCallActionSheet: () => Prom } }; - useEffect(() => { - handleShowCallOption(); - }, []); - - return { showInitCallActionSheet, showCallOption }; + return { showInitCallActionSheet, callEnabled, disabledTooltip }; }; diff --git a/app/lib/hooks/useVideoConf/useVideoConfCall.ts b/app/lib/hooks/useVideoConf/useVideoConfCall.ts new file mode 100644 index 000000000..e14bdfb37 --- /dev/null +++ b/app/lib/hooks/useVideoConf/useVideoConfCall.ts @@ -0,0 +1,68 @@ +import { useEffect, useState } from 'react'; + +import { SubscriptionType } from '../../../definitions'; +import { getUserSelector } from '../../../selectors/login'; +import { getSubscriptionByRoomId } from '../../database/services/Subscription'; +import { compareServerVersion, isReadOnly } from '../../methods/helpers'; +import { useAppSelector } from '../useAppSelector'; +import { usePermissions } from '../usePermissions'; +import { useSetting } from '../useSetting'; +import { isRoomFederated } from '../../methods'; + +export const useVideoConfCall = (rid: string): { callEnabled: boolean; disabledTooltip?: boolean } => { + const [callEnabled, setCallEnabled] = useState(false); + const [disabledTooltip, setDisabledTooltip] = useState(false); + + // OLD SETTINGS + const jitsiEnabled = useSetting('Jitsi_Enabled'); + const jitsiEnableTeams = useSetting('Jitsi_Enable_Teams'); + const jitsiEnableChannels = useSetting('Jitsi_Enable_Channels'); + + // NEW SETTINGS + // Only disable video conf if the settings are explicitly FALSE - any falsy value counts as true + const enabledDMs = useSetting('VideoConf_Enable_DMs') !== false; + const enabledChannel = useSetting('VideoConf_Enable_Channels') !== false; + const enabledTeams = useSetting('VideoConf_Enable_Teams') !== false; + const enabledGroups = useSetting('VideoConf_Enable_Groups') !== false; + const enabledLiveChat = useSetting('Omnichannel_call_provider') === 'default-provider'; + + const serverVersion = useAppSelector(state => state.server.version); + const isServer5OrNewer = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0'); + const [canStartCall] = usePermissions(['call-management'], rid); + const user = useAppSelector(state => getUserSelector(state)); + + const init = async () => { + const room = await getSubscriptionByRoomId(rid); + if (room) { + if (isServer5OrNewer) { + const isReadyOnly = await isReadOnly(room, user.username); + const ownUser = room.uids && room.uids.length === 1; + const enabled = enabledDMs || enabledChannel || enabledTeams || enabledGroups || enabledLiveChat; + const enableOption = enabled && canStartCall && (!user?.username || !room.muted?.includes(user.username)); + const federated = isRoomFederated(room); + + if (enableOption && !ownUser) { + if (federated || (room.ro && isReadyOnly)) { + setDisabledTooltip(true); + } + return setCallEnabled(true); + } + return; + } + // OLD SERVERS VERSIONS + const isJitsiDisabledForTeams = room.teamMain && !jitsiEnableTeams; + const isJitsiDisabledForChannels = !room.teamMain && (room.t === 'p' || room.t === 'c') && !jitsiEnableChannels; + + if (room.t === SubscriptionType.DIRECT) return setCallEnabled(!!jitsiEnabled); + if (room.t === SubscriptionType.CHANNEL) return setCallEnabled(!isJitsiDisabledForChannels); + if (room.t === SubscriptionType.GROUP) return setCallEnabled(!isJitsiDisabledForTeams); + } + return setCallEnabled(false); + }; + + useEffect(() => { + init(); + }, []); + + return { callEnabled, disabledTooltip }; +}; diff --git a/app/lib/methods/getPermissions.ts b/app/lib/methods/getPermissions.ts index a73146923..aab7a432c 100644 --- a/app/lib/methods/getPermissions.ts +++ b/app/lib/methods/getPermissions.ts @@ -58,7 +58,8 @@ export const SUPPORTED_PERMISSIONS = [ 'edit-livechat-room-customfields', 'view-canned-responses', 'mobile-upload-file', - 'delete-own-message' + 'delete-own-message', + 'call-management' ] as const; export async function setPermissions(): Promise { diff --git a/app/lib/methods/index.ts b/app/lib/methods/index.ts index 602d78f16..a1ac3ae1a 100644 --- a/app/lib/methods/index.ts +++ b/app/lib/methods/index.ts @@ -37,3 +37,4 @@ export * from './crashReport'; export * from './parseSettings'; export * from './subscribeRooms'; export * from './serializeAsciiUrl'; +export * from './isRoomFederated'; diff --git a/app/lib/methods/isRoomFederated.ts b/app/lib/methods/isRoomFederated.ts new file mode 100644 index 000000000..9047605f2 --- /dev/null +++ b/app/lib/methods/isRoomFederated.ts @@ -0,0 +1,8 @@ +import { ISubscription } from '../../definitions'; + +interface IRoomFederated extends ISubscription { + federated: true; +} + +export const isRoomFederated = (room: ISubscription): room is IRoomFederated => + 'federated' in room && (room as any).federated === true; diff --git a/app/views/RoomActionsView/components/CallSection.tsx b/app/views/RoomActionsView/components/CallSection.tsx index 3457124c5..be75067a5 100644 --- a/app/views/RoomActionsView/components/CallSection.tsx +++ b/app/views/RoomActionsView/components/CallSection.tsx @@ -4,9 +4,8 @@ import * as List from '../../../containers/List'; import { useVideoConf } from '../../../lib/hooks/useVideoConf'; export default function CallSection({ rid }: { rid: string }): React.ReactElement | null { - const { showCallOption, showInitCallActionSheet } = useVideoConf(rid); - - if (showCallOption) + const { callEnabled, showInitCallActionSheet, disabledTooltip } = useVideoConf(rid); + if (callEnabled) return ( @@ -16,6 +15,7 @@ export default function CallSection({ rid }: { rid: string }): React.ReactElemen testID='room-actions-call' left={() => } showActionIndicator + disabled={disabledTooltip} /> diff --git a/app/views/RoomInfoView/components/UserInfoButton.tsx b/app/views/RoomInfoView/components/UserInfoButton.tsx index 7185c8112..39a8000e6 100644 --- a/app/views/RoomInfoView/components/UserInfoButton.tsx +++ b/app/views/RoomInfoView/components/UserInfoButton.tsx @@ -16,20 +16,22 @@ function UserInfoButton({ iconName, onPress, label, - showIcon + showIcon, + enabled = true }: { danger?: boolean; iconName: TIconsName; onPress?: (prop: any) => void; label: string; showIcon?: boolean; + enabled?: boolean; }): React.ReactElement | null { const { colors } = useTheme(); - const color = danger ? colors.dangerColor : colors.actionTintColor; - + let color = danger ? colors.dangerColor : colors.actionTintColor; + if (!enabled) color = colors.auxiliaryText; if (showIcon) return ( - + {label} @@ -38,11 +40,19 @@ function UserInfoButton({ } export function CallButton({ rid, isDirect }: { rid: string; isDirect: boolean }): React.ReactElement | null { - const { showCallOption, showInitCallActionSheet } = useVideoConf(rid); + const { callEnabled, showInitCallActionSheet, disabledTooltip } = useVideoConf(rid); const serverVersion = useAppSelector(state => state.server.version); const greaterThanFive = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0'); - const showIcon = greaterThanFive ? showCallOption : showCallOption && isDirect; + const showIcon = greaterThanFive ? callEnabled : callEnabled && isDirect; - return ; + return ( + + ); } diff --git a/app/views/RoomView/components/HeaderCallButton.tsx b/app/views/RoomView/components/HeaderCallButton.tsx index 83fbb1241..5f43e9552 100644 --- a/app/views/RoomView/components/HeaderCallButton.tsx +++ b/app/views/RoomView/components/HeaderCallButton.tsx @@ -4,9 +4,16 @@ import * as HeaderButton from '../../../containers/HeaderButton'; import { useVideoConf } from '../../../lib/hooks/useVideoConf'; export default function HeaderCallButton({ rid }: { rid: string }): React.ReactElement | null { - const { showInitCallActionSheet, showCallOption } = useVideoConf(rid); + const { showInitCallActionSheet, callEnabled, disabledTooltip } = useVideoConf(rid); - if (showCallOption) - return ; + if (callEnabled) + return ( + + ); return null; }