2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2020 Signal Messenger, LLC
|
2021-05-20 21:54:03 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { ActiveCallType } from '../types/Calling';
|
|
|
|
import { CallMode, GroupCallConnectionState } from '../types/Calling';
|
|
|
|
import type { ConversationType } from '../state/ducks/conversations';
|
|
|
|
import type { LocalizerType } from '../types/Util';
|
2022-02-25 18:37:15 +00:00
|
|
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
2022-01-07 18:01:23 +00:00
|
|
|
import { CallingToast, DEFAULT_LIFETIME } from './CallingToast';
|
2021-05-20 21:54:03 +00:00
|
|
|
|
|
|
|
type PropsType = {
|
|
|
|
activeCall: ActiveCallType;
|
|
|
|
i18n: LocalizerType;
|
|
|
|
};
|
|
|
|
|
|
|
|
type ToastType =
|
|
|
|
| {
|
|
|
|
message: string;
|
|
|
|
type: 'dismissable' | 'static';
|
|
|
|
}
|
|
|
|
| undefined;
|
|
|
|
|
|
|
|
function getReconnectingToast({ activeCall, i18n }: PropsType): ToastType {
|
|
|
|
if (
|
|
|
|
activeCall.callMode === CallMode.Group &&
|
|
|
|
activeCall.connectionState === GroupCallConnectionState.Reconnecting
|
|
|
|
) {
|
|
|
|
return {
|
|
|
|
message: i18n('callReconnecting'),
|
|
|
|
type: 'static',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ME = Symbol('me');
|
|
|
|
|
|
|
|
function getCurrentPresenter(
|
|
|
|
activeCall: Readonly<ActiveCallType>
|
|
|
|
): ConversationType | typeof ME | undefined {
|
|
|
|
if (activeCall.presentingSource) {
|
|
|
|
return ME;
|
|
|
|
}
|
|
|
|
if (activeCall.callMode === CallMode.Direct) {
|
|
|
|
const isOtherPersonPresenting = activeCall.remoteParticipants.some(
|
|
|
|
participant => participant.presenting
|
|
|
|
);
|
|
|
|
return isOtherPersonPresenting ? activeCall.conversation : undefined;
|
|
|
|
}
|
|
|
|
if (activeCall.callMode === CallMode.Group) {
|
|
|
|
return activeCall.remoteParticipants.find(
|
|
|
|
participant => participant.presenting
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
function useScreenSharingToast({ activeCall, i18n }: PropsType): ToastType {
|
|
|
|
const [result, setResult] = useState<undefined | ToastType>(undefined);
|
|
|
|
|
|
|
|
const [previousPresenter, setPreviousPresenter] = useState<
|
|
|
|
undefined | { id: string | typeof ME; title?: string }
|
|
|
|
>(undefined);
|
|
|
|
|
|
|
|
const previousPresenterId = previousPresenter?.id;
|
|
|
|
const previousPresenterTitle = previousPresenter?.title;
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const currentPresenter = getCurrentPresenter(activeCall);
|
|
|
|
if (!currentPresenter && previousPresenterId) {
|
|
|
|
if (previousPresenterId === ME) {
|
|
|
|
setResult({
|
|
|
|
type: 'dismissable',
|
|
|
|
message: i18n('calling__presenting--you-stopped'),
|
|
|
|
});
|
|
|
|
} else if (previousPresenterTitle) {
|
|
|
|
setResult({
|
|
|
|
type: 'dismissable',
|
|
|
|
message: i18n('calling__presenting--person-stopped', [
|
|
|
|
previousPresenterTitle,
|
|
|
|
]),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [activeCall, i18n, previousPresenterId, previousPresenterTitle]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const currentPresenter = getCurrentPresenter(activeCall);
|
|
|
|
if (currentPresenter === ME) {
|
|
|
|
setPreviousPresenter({
|
|
|
|
id: ME,
|
|
|
|
});
|
|
|
|
} else if (!currentPresenter) {
|
|
|
|
setPreviousPresenter(undefined);
|
|
|
|
} else {
|
|
|
|
const { id, title } = currentPresenter;
|
|
|
|
setPreviousPresenter({ id, title });
|
|
|
|
}
|
|
|
|
}, [activeCall]);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In the future, this component should show toasts when users join or leave. See
|
|
|
|
// DESKTOP-902.
|
2022-11-18 00:45:19 +00:00
|
|
|
export function CallingToastManager(props: PropsType): JSX.Element {
|
2021-05-20 21:54:03 +00:00
|
|
|
const reconnectingToast = getReconnectingToast(props);
|
|
|
|
const screenSharingToast = useScreenSharingToast(props);
|
|
|
|
|
|
|
|
let toast: ToastType;
|
|
|
|
if (reconnectingToast) {
|
|
|
|
toast = reconnectingToast;
|
|
|
|
} else if (screenSharingToast) {
|
|
|
|
toast = screenSharingToast;
|
|
|
|
}
|
|
|
|
|
2022-11-23 20:13:13 +00:00
|
|
|
const [isVisible, setIsVisible] = useState(false);
|
2021-05-20 21:54:03 +00:00
|
|
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
|
|
|
|
const dismissToast = useCallback(() => {
|
|
|
|
if (timeoutRef) {
|
2022-11-23 20:13:13 +00:00
|
|
|
setIsVisible(false);
|
2021-05-20 21:54:03 +00:00
|
|
|
}
|
2022-11-23 20:13:13 +00:00
|
|
|
}, [setIsVisible, timeoutRef]);
|
2021-05-20 21:54:03 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
2022-11-23 20:13:13 +00:00
|
|
|
setIsVisible(toast !== undefined);
|
|
|
|
}, [toast]);
|
2021-05-20 21:54:03 +00:00
|
|
|
|
2022-11-23 20:13:13 +00:00
|
|
|
useEffect(() => {
|
|
|
|
if (toast?.type === 'dismissable') {
|
|
|
|
clearTimeoutIfNecessary(timeoutRef.current);
|
|
|
|
timeoutRef.current = setTimeout(dismissToast, DEFAULT_LIFETIME);
|
2021-05-20 21:54:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return () => {
|
2022-02-25 18:37:15 +00:00
|
|
|
clearTimeoutIfNecessary(timeoutRef.current);
|
2021-05-20 21:54:03 +00:00
|
|
|
};
|
2022-11-23 20:13:13 +00:00
|
|
|
}, [dismissToast, setIsVisible, timeoutRef, toast]);
|
2021-05-20 21:54:03 +00:00
|
|
|
|
|
|
|
return (
|
2022-11-23 20:13:13 +00:00
|
|
|
<CallingToast isVisible={isVisible} onClick={dismissToast}>
|
|
|
|
{toast?.message}
|
2022-01-07 18:01:23 +00:00
|
|
|
</CallingToast>
|
2021-05-20 21:54:03 +00:00
|
|
|
);
|
2022-11-18 00:45:19 +00:00
|
|
|
}
|