// Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback, useEffect, useRef, useState } from 'react'; 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'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { CallingToast, DEFAULT_LIFETIME } from './CallingToast'; 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('icu:callReconnecting'), type: 'static', }; } return undefined; } const ME = Symbol('me'); function getCurrentPresenter( activeCall: Readonly ): 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); 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('icu:calling__presenting--you-stopped'), }); } else if (previousPresenterTitle) { setResult({ type: 'dismissable', message: i18n('icu:calling__presenting--person-stopped', { name: 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. export function CallingToastManager(props: PropsType): JSX.Element { const reconnectingToast = getReconnectingToast(props); const screenSharingToast = useScreenSharingToast(props); let toast: ToastType; if (reconnectingToast) { toast = reconnectingToast; } else if (screenSharingToast) { toast = screenSharingToast; } const [isVisible, setIsVisible] = useState(false); const timeoutRef = useRef(null); const dismissToast = useCallback(() => { if (timeoutRef) { setIsVisible(false); } }, [setIsVisible, timeoutRef]); useEffect(() => { setIsVisible(toast !== undefined); }, [toast]); useEffect(() => { if (toast?.type === 'dismissable') { clearTimeoutIfNecessary(timeoutRef.current); timeoutRef.current = setTimeout(dismissToast, DEFAULT_LIFETIME); } return () => { clearTimeoutIfNecessary(timeoutRef.current); }; }, [dismissToast, setIsVisible, timeoutRef, toast]); return ( {toast?.message} ); }