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 = {
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;

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

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',
'view-canned-responses',
'mobile-upload-file',
'delete-own-message'
'delete-own-message',
'call-management'
] as const;
export async function setPermissions(): Promise<void> {

View File

@ -37,3 +37,4 @@ export * from './crashReport';
export * from './parseSettings';
export * from './subscribeRooms';
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';
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 (
<List.Section>
<List.Separator />
@ -16,6 +15,7 @@ export default function CallSection({ rid }: { rid: string }): React.ReactElemen
testID='room-actions-call'
left={() => <List.Icon name='phone' />}
showActionIndicator
disabled={disabledTooltip}
/>
<List.Separator />
</List.Section>

View File

@ -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 (
<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} />
<Text style={[styles.roomButtonText, { color }]}>{label}</Text>
</BorderlessButton>
@ -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 <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';
export default function HeaderCallButton({ rid }: { rid: string }): React.ReactElement | null {
const { showInitCallActionSheet, showCallOption } = useVideoConf(rid);
const { showInitCallActionSheet, callEnabled, disabledTooltip } = useVideoConf(rid);
if (showCallOption)
return <HeaderButton.Item iconName='phone' onPress={showInitCallActionSheet} testID='room-view-header-call' />;
if (callEnabled)
return (
<HeaderButton.Item
disabled={disabledTooltip}
iconName='phone'
onPress={showInitCallActionSheet}
testID='room-view-header-call'
/>
);
return null;
}