Fix call link delete to require inactive call

This commit is contained in:
ayumi-signal 2024-10-18 13:19:45 -07:00 committed by GitHub
parent 902c1f4634
commit 3f8b8bdb2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 98 additions and 37 deletions

View file

@ -24,6 +24,7 @@ export default {
callHistoryGroup: getFakeCallLinkHistoryGroup(), callHistoryGroup: getFakeCallLinkHistoryGroup(),
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY, callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
isAnybodyInCall: false, isAnybodyInCall: false,
isCallActiveOnServer: false,
isInCall: false, isInCall: false,
isInAnotherCall: false, isInAnotherCall: false,
onDeleteCallLink: action('onDeleteCallLink'), onDeleteCallLink: action('onDeleteCallLink'),
@ -39,11 +40,19 @@ export function Admin(args: CallLinkDetailsProps): JSX.Element {
} }
export function AdminAndCallActive(args: CallLinkDetailsProps): JSX.Element { export function AdminAndCallActive(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} isAnybodyInCall />; return <CallLinkDetails {...args} isAnybodyInCall isCallActiveOnServer />;
} }
export function AdminAndInCall(args: CallLinkDetailsProps): JSX.Element { export function AdminAndInCall(args: CallLinkDetailsProps): JSX.Element {
return <CallLinkDetails {...args} isAnybodyInCall isInCall />; return (
<CallLinkDetails {...args} isAnybodyInCall isCallActiveOnServer isInCall />
);
}
export function AdminRecentlyEndedCall(
args: CallLinkDetailsProps
): JSX.Element {
return <CallLinkDetails {...args} isCallActiveOnServer />;
} }
export function NonAdmin(args: CallLinkDetailsProps): JSX.Element { export function NonAdmin(args: CallLinkDetailsProps): JSX.Element {
@ -52,13 +61,23 @@ export function NonAdmin(args: CallLinkDetailsProps): JSX.Element {
export function NonAdminAndCallActive(args: CallLinkDetailsProps): JSX.Element { export function NonAdminAndCallActive(args: CallLinkDetailsProps): JSX.Element {
return ( return (
<CallLinkDetails {...args} callLink={FAKE_CALL_LINK} isAnybodyInCall /> <CallLinkDetails
{...args}
callLink={FAKE_CALL_LINK}
isAnybodyInCall
isCallActiveOnServer
/>
); );
} }
export function InAnotherCall(args: CallLinkDetailsProps): JSX.Element { export function InAnotherCall(args: CallLinkDetailsProps): JSX.Element {
return ( return (
<CallLinkDetails {...args} callLink={FAKE_CALL_LINK} isInAnotherCall /> <CallLinkDetails
{...args}
callLink={FAKE_CALL_LINK}
isInAnotherCall
isCallActiveOnServer
/>
); );
} }
@ -70,6 +89,7 @@ export function InAnotherCallAndCallActive(
{...args} {...args}
callLink={FAKE_CALL_LINK} callLink={FAKE_CALL_LINK}
isAnybodyInCall isAnybodyInCall
isCallActiveOnServer
isInAnotherCall isInAnotherCall
/> />
); );

View file

@ -33,6 +33,7 @@ export type CallLinkDetailsProps = Readonly<{
callHistoryGroup: CallHistoryGroup; callHistoryGroup: CallHistoryGroup;
callLink: CallLinkType | undefined; callLink: CallLinkType | undefined;
isAnybodyInCall: boolean; isAnybodyInCall: boolean;
isCallActiveOnServer: boolean;
isInCall: boolean; isInCall: boolean;
isInAnotherCall: boolean; isInAnotherCall: boolean;
i18n: LocalizerType; i18n: LocalizerType;
@ -48,6 +49,7 @@ export function CallLinkDetails({
callLink, callLink,
i18n, i18n,
isAnybodyInCall, isAnybodyInCall,
isCallActiveOnServer,
isInCall, isInCall,
isInAnotherCall, isInAnotherCall,
onDeleteCallLink, onDeleteCallLink,
@ -88,7 +90,7 @@ export function CallLinkDetails({
); );
const callLinkRestrictionsSelect = ( const callLinkRestrictionsSelect = (
<CallLinkRestrictionsSelect <CallLinkRestrictionsSelect
disabled={isAnybodyInCall} disabled={isCallActiveOnServer}
i18n={i18n} i18n={i18n}
value={callLink.restrictions} value={callLink.restrictions}
onChange={onUpdateCallLinkRestrictions} onChange={onUpdateCallLinkRestrictions}
@ -159,7 +161,7 @@ export function CallLinkDetails({
} }
label={i18n('icu:CallLinkDetails__ApproveAllMembersLabel')} label={i18n('icu:CallLinkDetails__ApproveAllMembersLabel')}
right={ right={
isAnybodyInCall ? ( isCallActiveOnServer ? (
<Tooltip <Tooltip
className="CallLinkDetails__ApproveAllMembersDisabledTooltip" className="CallLinkDetails__ApproveAllMembersDisabledTooltip"
content={i18n( content={i18n(
@ -207,9 +209,9 @@ export function CallLinkDetails({
className={classNames({ className={classNames({
CallLinkDetails__DeleteLink: true, CallLinkDetails__DeleteLink: true,
'CallLinkDetails__DeleteLink--disabled-for-active-call': 'CallLinkDetails__DeleteLink--disabled-for-active-call':
isAnybodyInCall, isCallActiveOnServer,
})} })}
disabled={isAnybodyInCall} disabled={isCallActiveOnServer}
icon={ icon={
<ConversationDetailsIcon <ConversationDetailsIcon
ariaLabel={i18n('icu:CallLinkDetails__DeleteLink')} ariaLabel={i18n('icu:CallLinkDetails__DeleteLink')}
@ -217,7 +219,7 @@ export function CallLinkDetails({
/> />
} }
label={ label={
isAnybodyInCall ? ( isCallActiveOnServer ? (
<Tooltip <Tooltip
className="CallLinkDetails__DeleteLinkTooltip" className="CallLinkDetails__DeleteLinkTooltip"
content={i18n( content={i18n(

View file

@ -311,32 +311,49 @@ export function CallsList({
const { mode, peerId } = callHistoryGroup; const { mode, peerId } = callHistoryGroup;
const call = getCallByPeerId({ mode, peerId }); const call = getCallByPeerId({ mode, peerId });
if (!call) { if (!call || !isGroupOrAdhocCallState(call)) {
// We can't tell from CallHistory alone whether a 1:1 call is active
return false; return false;
} }
if (isGroupOrAdhocCallState(call)) { // eraId indicates a group/call link call is active.
if (!isAnybodyInGroupCall(call.peekInfo)) { const eraId = call.peekInfo?.eraId;
return false; if (!eraId) {
} return false;
if (mode === CallMode.Group) {
const eraId = call.peekInfo?.eraId;
if (!eraId) {
return false;
}
const callId = getCallIdFromEra(eraId);
return callHistoryGroup.children.some(
groupItem => groupItem.callId === callId
);
}
return true;
} }
// We can't tell from CallHistory alone whether a 1:1 call is active // Group calls have multiple entries sharing a peerId. To distinguish them we need
return false; // to compare the active callId (derived from eraId) with this item's callId set.
if (mode === CallMode.Group) {
const callId = getCallIdFromEra(eraId);
return callHistoryGroup.children.some(
groupItem => groupItem.callId === callId
);
}
// Call links only show once in the calls list, so we can just return active.
return true;
},
[getCallByPeerId]
);
const getIsAnybodyInCall = useCallback(
({
callHistoryGroup,
}: {
callHistoryGroup: CallHistoryGroup | null;
}): boolean => {
if (!callHistoryGroup) {
return false;
}
const { mode, peerId } = callHistoryGroup;
const call = getCallByPeerId({ mode, peerId });
if (!call || !isGroupOrAdhocCallState(call)) {
return false;
}
return isAnybodyInGroupCall(call.peekInfo);
}, },
[getCallByPeerId] [getCallByPeerId]
); );
@ -370,7 +387,7 @@ export function CallsList({
); );
} }
// For group and adhoc calls, a call has to have members in it (see getIsCallActive) // For group and adhoc calls
return Boolean( return Boolean(
isActive && isActive &&
conversation && conversation &&
@ -433,8 +450,9 @@ export function CallsList({
for (const item of callItems) { for (const item of callItems) {
const { mode } = item; const { mode } = item;
if (isGroupOrAdhocCallMode(mode)) { if (isGroupOrAdhocCallMode(mode)) {
const isActive = getIsCallActive({ callHistoryGroup: item }); const isActive = getIsCallActive({
callHistoryGroup: item,
});
if (isActive) { if (isActive) {
// Don't peek if you're already in the call. // Don't peek if you're already in the call.
const activeCallConversationId = activeCall?.conversationId; const activeCallConversationId = activeCall?.conversationId;
@ -711,6 +729,13 @@ export function CallsList({
const isActive = getIsCallActive({ const isActive = getIsCallActive({
callHistoryGroup: item, callHistoryGroup: item,
}); });
// After everyone leaves a call, it remains active on the server for a little bit.
// We don't need to show the active call join button in this case.
const isAnybodyInCall =
isActive &&
getIsAnybodyInCall({
callHistoryGroup: item,
});
const isInCall = getIsInCall({ const isInCall = getIsInCall({
activeCallConversationId, activeCallConversationId,
callHistoryGroup: item, callHistoryGroup: item,
@ -722,7 +747,9 @@ export function CallsList({
const isCallButtonVisible = Boolean( const isCallButtonVisible = Boolean(
!isAdhoc || (isAdhoc && getCallLink(item.peerId)) !isAdhoc || (isAdhoc && getCallLink(item.peerId))
); );
const isActiveVisible = Boolean(isCallButtonVisible && item && isActive); const isActiveVisible = Boolean(
isCallButtonVisible && item && isAnybodyInCall
);
if (searchPending || item == null || conversation == null) { if (searchPending || item == null || conversation == null) {
return ( return (
@ -887,6 +914,7 @@ export function CallsList({
searchPending, searchPending,
getCallLink, getCallLink,
getConversationForItem, getConversationForItem,
getIsAnybodyInCall,
getIsCallActive, getIsCallActive,
getIsInCall, getIsInCall,
selectedCallHistoryGroup, selectedCallHistoryGroup,

View file

@ -54,3 +54,9 @@ export const isAnybodyInGroupCall = (
} }
return peekInfo.acis.length > 0; return peekInfo.acis.length > 0;
}; };
export const isGroupCallActiveOnServer = (
peekInfo: undefined | Readonly<Pick<GroupCallPeekInfoType, 'eraId'>>
): boolean => {
return Boolean(peekInfo?.eraId);
};

View file

@ -14,7 +14,10 @@ import { useGlobalModalActions } from '../ducks/globalModals';
import { useCallingActions } from '../ducks/calling'; import { useCallingActions } from '../ducks/calling';
import { strictAssert } from '../../util/assert'; import { strictAssert } from '../../util/assert';
import type { CallLinkRestrictions } from '../../types/CallLink'; import type { CallLinkRestrictions } from '../../types/CallLink';
import { isAnybodyInGroupCall } from '../ducks/callingHelpers'; import {
isAnybodyInGroupCall,
isGroupCallActiveOnServer,
} from '../ducks/callingHelpers';
export type SmartCallLinkDetailsProps = Readonly<{ export type SmartCallLinkDetailsProps = Readonly<{
roomId: string; roomId: string;
@ -66,7 +69,8 @@ export const SmartCallLinkDetails = memo(function SmartCallLinkDetails({
const adhocCallSelector = useSelector(getAdhocCallSelector); const adhocCallSelector = useSelector(getAdhocCallSelector);
const adhocCall = adhocCallSelector(roomId); const adhocCall = adhocCallSelector(roomId);
const hasActiveCall = isAnybodyInGroupCall(adhocCall?.peekInfo); const isAnybodyInCall = isAnybodyInGroupCall(adhocCall?.peekInfo);
const isCallActiveOnServer = isGroupCallActiveOnServer(adhocCall?.peekInfo);
const activeCall = useSelector(getActiveCallState); const activeCall = useSelector(getActiveCallState);
const isInAnotherCall = Boolean( const isInAnotherCall = Boolean(
@ -80,7 +84,8 @@ export const SmartCallLinkDetails = memo(function SmartCallLinkDetails({
<CallLinkDetails <CallLinkDetails
callHistoryGroup={callHistoryGroup} callHistoryGroup={callHistoryGroup}
callLink={callLink} callLink={callLink}
isAnybodyInCall={hasActiveCall} isAnybodyInCall={isAnybodyInCall}
isCallActiveOnServer={isCallActiveOnServer}
isInCall={isInCall} isInCall={isInCall}
isInAnotherCall={isInAnotherCall} isInAnotherCall={isInAnotherCall}
i18n={i18n} i18n={i18n}