feat: align mobile videoConf settings with web settings (#5136)

* add new videoConf settings

* add call-management permission

* create useSetting hook

* create useVideoConfCall hook

* use new videoconf settings

* change isAdmin check

* ignore older versions
This commit is contained in:
Gleidson Daniel Silva 2023-08-21 16:12:34 -03:00 committed by GitHub
parent 1de24befe5
commit 25c3f46750
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 177 additions and 88 deletions

View File

@ -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 = { export const defaultSettings = {
Accounts_AllowEmailChange: { Accounts_AllowEmailChange: {
type: 'valueAsBoolean' type: 'valueAsBoolean'
@ -90,24 +117,6 @@ export const defaultSettings = {
Livechat_request_comment_when_closing_conversation: { Livechat_request_comment_when_closing_conversation: {
type: 'valueAsBoolean' 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: { Message_AllowDeleting: {
type: 'valueAsBoolean' type: 'valueAsBoolean'
}, },
@ -198,12 +207,6 @@ export const defaultSettings = {
Accounts_AllowInvisibleStatusOption: { Accounts_AllowInvisibleStatusOption: {
type: 'valueAsString' type: 'valueAsString'
}, },
Jitsi_Enable_Teams: {
type: 'valueAsBoolean'
},
Jitsi_Enable_Channels: {
type: 'valuesAsBoolean'
},
Canned_Responses_Enable: { Canned_Responses_Enable: {
type: 'valueAsBoolean' type: 'valueAsBoolean'
}, },
@ -236,5 +239,9 @@ export const defaultSettings = {
}, },
Presence_broadcast_disabled: { Presence_broadcast_disabled: {
type: 'valueAsBoolean' type: 'valueAsBoolean'
} },
Omnichannel_call_provider: {
type: 'valueAsBoolean'
},
...deprecatedSettings
} as const; } as const;

View File

@ -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;
}

View File

@ -1,17 +1,16 @@
import { Camera } from 'expo-camera'; import { Camera } from 'expo-camera';
import React, { useEffect, useState } from 'react'; import React from 'react';
import { useActionSheet } from '../../../containers/ActionSheet'; import { useActionSheet } from '../../../containers/ActionSheet';
import { SubscriptionType } from '../../../definitions';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { getUserSelector } from '../../../selectors/login'; import { getUserSelector } from '../../../selectors/login';
import { getSubscriptionByRoomId } from '../../database/services/Subscription';
import { compareServerVersion, showErrorAlert } from '../../methods/helpers'; import { compareServerVersion, showErrorAlert } from '../../methods/helpers';
import { handleAndroidBltPermission } from '../../methods/videoConf'; import { handleAndroidBltPermission } from '../../methods/videoConf';
import { Services } from '../../services'; import { Services } from '../../services';
import { useAppSelector } from '../useAppSelector'; import { useAppSelector } from '../useAppSelector';
import { useSnaps } from '../useSnaps'; import { useSnaps } from '../useSnaps';
import StartACallActionSheet from './StartACallActionSheet'; import StartACallActionSheet from './StartACallActionSheet';
import { useVideoConfCall } from './useVideoConfCall';
const availabilityErrors = { const availabilityErrors = {
NOT_CONFIGURED: 'video-conf-provider-not-configured', 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`)); return showErrorAlert(i18n.t(`${error}-body`), i18n.t(`${error}-header`));
}; };
export const useVideoConf = (rid: string): { showInitCallActionSheet: () => Promise<void>; showCallOption: boolean } => { export const useVideoConf = (
const [showCallOption, setShowCallOption] = useState(false); rid: string
): { showInitCallActionSheet: () => Promise<void>; callEnabled: boolean; disabledTooltip?: boolean } => {
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);
const user = useAppSelector(state => getUserSelector(state)); const user = useAppSelector(state => getUserSelector(state));
const serverVersion = useAppSelector(state => state.server.version);
const { callEnabled, disabledTooltip } = useVideoConfCall(rid);
const [permission, requestPermission] = Camera.useCameraPermissions(); const [permission, requestPermission] = Camera.useCameraPermissions();
const isServer5OrNewer = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0');
const { showActionSheet } = useActionSheet(); const { showActionSheet } = useActionSheet();
const snaps = useSnaps(404); const snaps = useSnaps(404);
const handleShowCallOption = async () => { const isServer5OrNewer = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0');
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 canInitAnCall = async () => { const canInitAnCall = async () => {
if (isServer5OrNewer) { if (callEnabled) {
try { if (isServer5OrNewer) {
await Services.videoConferenceGetCapabilities(); try {
return true; await Services.videoConferenceGetCapabilities();
} catch (error: any) { return true;
const isAdmin = !!['admin'].find(role => user.roles?.includes(role)); } catch (error: any) {
switch (error?.error) { const isAdmin = !!user.roles?.includes('admin');
case availabilityErrors.NOT_CONFIGURED: switch (error?.error) {
return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED); case availabilityErrors.NOT_CONFIGURED:
case availabilityErrors.NOT_ACTIVE: return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED);
return handleErrors(isAdmin, availabilityErrors.NOT_ACTIVE); case availabilityErrors.NOT_ACTIVE:
case availabilityErrors.NO_APP: return handleErrors(isAdmin, availabilityErrors.NOT_ACTIVE);
return handleErrors(isAdmin, availabilityErrors.NO_APP); case availabilityErrors.NO_APP:
default: return handleErrors(isAdmin, availabilityErrors.NO_APP);
return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED); default:
return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED);
}
} }
} }
return true;
} }
return true; return false;
}; };
const showInitCallActionSheet = async () => { const showInitCallActionSheet = async () => {
@ -92,9 +77,5 @@ export const useVideoConf = (rid: string): { showInitCallActionSheet: () => Prom
} }
}; };
useEffect(() => { return { showInitCallActionSheet, callEnabled, disabledTooltip };
handleShowCallOption();
}, []);
return { showInitCallActionSheet, showCallOption };
}; };

View File

@ -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 };
};

View File

@ -58,7 +58,8 @@ export const SUPPORTED_PERMISSIONS = [
'edit-livechat-room-customfields', 'edit-livechat-room-customfields',
'view-canned-responses', 'view-canned-responses',
'mobile-upload-file', 'mobile-upload-file',
'delete-own-message' 'delete-own-message',
'call-management'
] as const; ] as const;
export async function setPermissions(): Promise<void> { export async function setPermissions(): Promise<void> {

View File

@ -37,3 +37,4 @@ export * from './crashReport';
export * from './parseSettings'; export * from './parseSettings';
export * from './subscribeRooms'; export * from './subscribeRooms';
export * from './serializeAsciiUrl'; export * from './serializeAsciiUrl';
export * from './isRoomFederated';

View File

@ -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;

View File

@ -4,9 +4,8 @@ import * as List from '../../../containers/List';
import { useVideoConf } from '../../../lib/hooks/useVideoConf'; import { useVideoConf } from '../../../lib/hooks/useVideoConf';
export default function CallSection({ rid }: { rid: string }): React.ReactElement | null { export default function CallSection({ rid }: { rid: string }): React.ReactElement | null {
const { showCallOption, showInitCallActionSheet } = useVideoConf(rid); const { callEnabled, showInitCallActionSheet, disabledTooltip } = useVideoConf(rid);
if (callEnabled)
if (showCallOption)
return ( return (
<List.Section> <List.Section>
<List.Separator /> <List.Separator />
@ -16,6 +15,7 @@ export default function CallSection({ rid }: { rid: string }): React.ReactElemen
testID='room-actions-call' testID='room-actions-call'
left={() => <List.Icon name='phone' />} left={() => <List.Icon name='phone' />}
showActionIndicator showActionIndicator
disabled={disabledTooltip}
/> />
<List.Separator /> <List.Separator />
</List.Section> </List.Section>

View File

@ -16,20 +16,22 @@ function UserInfoButton({
iconName, iconName,
onPress, onPress,
label, label,
showIcon showIcon,
enabled = true
}: { }: {
danger?: boolean; danger?: boolean;
iconName: TIconsName; iconName: TIconsName;
onPress?: (prop: any) => void; onPress?: (prop: any) => void;
label: string; label: string;
showIcon?: boolean; showIcon?: boolean;
enabled?: boolean;
}): React.ReactElement | null { }): React.ReactElement | null {
const { colors } = useTheme(); const { colors } = useTheme();
const color = danger ? colors.dangerColor : colors.actionTintColor; let color = danger ? colors.dangerColor : colors.actionTintColor;
if (!enabled) color = colors.auxiliaryText;
if (showIcon) if (showIcon)
return ( return (
<BorderlessButton testID={`room-info-view-${iconName}`} onPress={onPress} style={styles.roomButton}> <BorderlessButton enabled={enabled} testID={`room-info-view-${iconName}`} onPress={onPress} style={styles.roomButton}>
<CustomIcon name={iconName} size={30} color={color} /> <CustomIcon name={iconName} size={30} color={color} />
<Text style={[styles.roomButtonText, { color }]}>{label}</Text> <Text style={[styles.roomButtonText, { color }]}>{label}</Text>
</BorderlessButton> </BorderlessButton>
@ -38,11 +40,19 @@ function UserInfoButton({
} }
export function CallButton({ rid, isDirect }: { rid: string; isDirect: boolean }): React.ReactElement | null { 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 serverVersion = useAppSelector(state => state.server.version);
const greaterThanFive = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0'); const greaterThanFive = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0');
const showIcon = greaterThanFive ? showCallOption : showCallOption && isDirect; const showIcon = greaterThanFive ? callEnabled : callEnabled && isDirect;
return <UserInfoButton onPress={showInitCallActionSheet} iconName='phone' label={i18n.t('Call')} showIcon={showIcon} />; return (
<UserInfoButton
enabled={!disabledTooltip}
onPress={showInitCallActionSheet}
iconName='phone'
label={i18n.t('Call')}
showIcon={showIcon}
/>
);
} }

View File

@ -4,9 +4,16 @@ import * as HeaderButton from '../../../containers/HeaderButton';
import { useVideoConf } from '../../../lib/hooks/useVideoConf'; import { useVideoConf } from '../../../lib/hooks/useVideoConf';
export default function HeaderCallButton({ rid }: { rid: string }): React.ReactElement | null { export default function HeaderCallButton({ rid }: { rid: string }): React.ReactElement | null {
const { showInitCallActionSheet, showCallOption } = useVideoConf(rid); const { showInitCallActionSheet, callEnabled, disabledTooltip } = useVideoConf(rid);
if (showCallOption) if (callEnabled)
return <HeaderButton.Item iconName='phone' onPress={showInitCallActionSheet} testID='room-view-header-call' />; return (
<HeaderButton.Item
disabled={disabledTooltip}
iconName='phone'
onPress={showInitCallActionSheet}
testID='room-view-header-call'
/>
);
return null; return null;
} }