From 00d96888e7f552aa87d92e955401f2b7fb35ddbf Mon Sep 17 00:00:00 2001 From: trevor-signal <131492920+trevor-signal@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:26:25 -0400 Subject: [PATCH] Add new toast region for calling button toasts --- stylesheets/components/CallControls.scss | 2 +- stylesheets/components/CallingToast.scss | 18 +++-- ts/components/CallScreen.tsx | 10 ++- ts/components/CallingLobby.tsx | 57 ++------------- ts/components/CallingToast.tsx | 25 ++++--- ts/components/CallingToastManager.tsx | 90 ++++++++++++++++++++---- ts/util/lint/exceptions.json | 7 ++ 7 files changed, 129 insertions(+), 80 deletions(-) diff --git a/stylesheets/components/CallControls.scss b/stylesheets/components/CallControls.scss index db95c0a3f610..a7bd8e628712 100644 --- a/stylesheets/components/CallControls.scss +++ b/stylesheets/components/CallControls.scss @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only .CallControls { - position: static; + position: relative; bottom: 0; display: flex; flex-grow: 0; diff --git a/stylesheets/components/CallingToast.scss b/stylesheets/components/CallingToast.scss index 06374567edb1..6bdbbbb428b6 100644 --- a/stylesheets/components/CallingToast.scss +++ b/stylesheets/components/CallingToast.scss @@ -25,12 +25,12 @@ } .CallingToast { - @include font-body-1; - background-color: $color-gray-75; + @include font-subtitle; + padding-block: 8px; + padding-inline: 12px; border-radius: 22px; - color: $color-white; - padding-block: 11px; - padding-inline: 20px; + background-color: $color-gray-80; + color: $color-gray-15; text-align: center; user-select: none; &__reconnecting { @@ -39,3 +39,11 @@ gap: 8px; } } + +.CallingButtonToasts .CallingToasts { + position: absolute; + top: -16px; + transform: translateY(-100%); + /* stylelint-disable-next-line liberty/use-logical-spec */ + left: 0; +} diff --git a/ts/components/CallScreen.tsx b/ts/components/CallScreen.tsx index 1e8913ad0020..a572f38afdba 100644 --- a/ts/components/CallScreen.tsx +++ b/ts/components/CallScreen.tsx @@ -35,7 +35,7 @@ import { import { AvatarColors } from '../types/Colors'; import type { ConversationType } from '../state/ducks/conversations'; import { - useMutedToast, + CallingButtonToastsContainer, useReconnectingToast, useScreenSharingStoppedToast, } from './CallingToastManager'; @@ -251,7 +251,6 @@ export function CallScreen({ }; }, [toggleAudio, toggleVideo]); - useMutedToast(hasLocalAudio, i18n); useReconnectingToast({ activeCall, i18n }); useScreenSharingStoppedToast({ activeCall, i18n }); @@ -546,6 +545,13 @@ export function CallScreen({
{conversation.title}
{callStatus}
+ + +
; @@ -228,9 +228,7 @@ export function CallingLobby({ toggleParticipants, ]); - useMutedToast(hasLocalAudio, i18n); useWasInitiallyMutedToast(hasLocalAudio, i18n); - useOutgoingRingToast(isRingButtonVisible, outgoingRing, i18n); return ( @@ -283,6 +281,11 @@ export function CallingLobby({
{conversation.title}
{callStatus}
+
(undefined); - const { showToast, hideToast } = useCallingToasts(); - const RINGING_TOAST_KEY = 'ringing'; - - React.useEffect(() => { - if (!isRingButtonVisible) { - return; - } - - setPreviousOutgoingRing(outgoingRing); - }, [isRingButtonVisible, outgoingRing]); - - React.useEffect(() => { - if (!isRingButtonVisible) { - return; - } - - if ( - previousOutgoingRing !== undefined && - outgoingRing !== previousOutgoingRing - ) { - hideToast(RINGING_TOAST_KEY); - showToast({ - key: RINGING_TOAST_KEY, - content: outgoingRing - ? i18n('icu:CallControls__RingingToast--ringing-on') - : i18n('icu:CallControls__RingingToast--ringing-off'), - autoClose: true, - dismissable: true, - }); - } - }, [ - isRingButtonVisible, - outgoingRing, - previousOutgoingRing, - hideToast, - showToast, - i18n, - ]); -} diff --git a/ts/components/CallingToast.tsx b/ts/components/CallingToast.tsx index d7c04700b036..971c2f016d4b 100644 --- a/ts/components/CallingToast.tsx +++ b/ts/components/CallingToast.tsx @@ -56,10 +56,12 @@ const CallingToastContext = createContext(null); export function CallingToastProvider({ i18n, children, + region, maxToasts = 5, }: { i18n: LocalizerType; children: React.ReactNode; + region?: React.RefObject; maxToasts?: number; }): JSX.Element { const [toasts, setToasts] = React.useState>([]); @@ -198,16 +200,20 @@ export function CallingToastProvider({ const transitions = useTransition(toasts, { from: item => ({ opacity: 0, + scale: 0.85, marginTop: // If this is the first toast shown, or if this is replacing the // first toast, we just fade-in (and don't slide down) - previousToasts.length === 0 || item.key === previousToasts[0].key + previousToasts.length === 0 || + item.key === previousToasts[0].key || + maxToasts === toasts.length ? '0px' : `${-1 * TOAST_HEIGHT_PX}px`, }), enter: { opacity: 1, zIndex: 1, + scale: 1, marginTop: '0px', config: (key: string) => { if (key === 'marginTop') { @@ -231,19 +237,22 @@ export function CallingToastProvider({ : `${-1 * (TOAST_HEIGHT_PX + TOAST_GAP_PX)}px`, // If this toast is being replaced by another one with the same key, immediately // hide it - display: toasts.some(toast => toast.key === item.key) - ? 'none' - : 'block', + display: + toasts.some(toast => toast.key === item.key) || + maxToasts === toasts.length + ? 'none' + : 'block', config: (key: string) => { if (key === 'zIndex') { return { duration: 0 }; } + if (key === 'display') { + return { duration: 0 }; + } if (key === 'opacity') { return { duration: 100 }; } - return { - duration: 300, - }; + return { duration: 200 }; }, }; }, @@ -278,7 +287,7 @@ export function CallingToastProvider({ ))}
, - document.body + region?.current ?? document.body )} {children} diff --git a/ts/components/CallingToastManager.tsx b/ts/components/CallingToastManager.tsx index 14606cf18ad4..c01bbdf97c3f 100644 --- a/ts/components/CallingToastManager.tsx +++ b/ts/components/CallingToastManager.tsx @@ -1,14 +1,15 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import type { ActiveCallType } from '../types/Calling'; import { CallMode } from '../types/Calling'; import type { ConversationType } from '../state/ducks/conversations'; import type { LocalizerType } from '../types/Util'; import { isReconnecting } from '../util/callingIsReconnecting'; -import { useCallingToasts } from './CallingToast'; +import { CallingToastProvider, useCallingToasts } from './CallingToast'; import { Spinner } from './Spinner'; +import { usePrevious } from '../hooks/usePrevious'; type PropsType = { activeCall: ActiveCallType; @@ -99,20 +100,17 @@ export function useScreenSharingStoppedToast({ }, [activeCall, previousPresenter, showToast, i18n]); } -export function useMutedToast( - hasLocalAudio: boolean, - i18n: LocalizerType -): void { - const [previousHasLocalAudio, setPreviousHasLocalAudio] = useState< - undefined | boolean - >(undefined); +function useMutedToast({ + hasLocalAudio, + i18n, +}: { + hasLocalAudio: boolean; + i18n: LocalizerType; +}): void { + const previousHasLocalAudio = usePrevious(hasLocalAudio, hasLocalAudio); const { showToast, hideToast } = useCallingToasts(); const MUTED_TOAST_KEY = 'muted'; - useEffect(() => { - setPreviousHasLocalAudio(hasLocalAudio); - }, [hasLocalAudio]); - useEffect(() => { if ( previousHasLocalAudio !== undefined && @@ -130,3 +128,69 @@ export function useMutedToast( } }, [hasLocalAudio, previousHasLocalAudio, hideToast, showToast, i18n]); } + +function useOutgoingRingToast({ + outgoingRing, + i18n, +}: { + outgoingRing?: boolean; + i18n: LocalizerType; +}): void { + const { showToast, hideToast } = useCallingToasts(); + const previousOutgoingRing = usePrevious(outgoingRing, outgoingRing); + const RINGING_TOAST_KEY = 'ringing'; + + React.useEffect(() => { + if (outgoingRing === undefined) { + return; + } + + if ( + previousOutgoingRing !== undefined && + outgoingRing !== previousOutgoingRing + ) { + hideToast(RINGING_TOAST_KEY); + showToast({ + key: RINGING_TOAST_KEY, + content: outgoingRing + ? i18n('icu:CallControls__RingingToast--ringing-on') + : i18n('icu:CallControls__RingingToast--ringing-off'), + autoClose: true, + dismissable: true, + }); + } + }, [outgoingRing, previousOutgoingRing, hideToast, showToast, i18n]); +} + +type CallingButtonToastsType = { + hasLocalAudio: boolean; + outgoingRing: boolean | undefined; + i18n: LocalizerType; +}; + +export function CallingButtonToastsContainer( + props: CallingButtonToastsType +): JSX.Element { + const toastRegionRef = useRef(null); + return ( + +
+ + + ); +} + +function CallingButtonToasts({ + hasLocalAudio, + outgoingRing, + i18n, +}: CallingButtonToastsType) { + useMutedToast({ hasLocalAudio, i18n }); + useOutgoingRingToast({ outgoingRing, i18n }); + + return null; +} diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 0c40950692d2..73c266bf1c5e 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -3020,6 +3020,13 @@ "reasonCategory": "usageTrusted", "updated": "2023-10-10T17:05:02.468Z" }, + { + "rule": "React-useRef", + "path": "ts/components/CallingToastManager.tsx", + "line": " const toastRegionRef = useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-26T13:57:41.860Z" + }, { "rule": "React-useRef", "path": "ts/components/CallsList.tsx",