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",