diff --git a/.env b/.env index 1f0fc11c..2f62d615 100644 --- a/.env +++ b/.env @@ -22,6 +22,7 @@ # VITE_THEME_PRIMARY_CONTENT=#ffffff # VITE_THEME_SECONDARY_CONTENT=#a9b2bc # VITE_THEME_TERTIARY_CONTENT=#8e99a4 +# VITE_THEME_TERTIARY_CONTENT_20=#8e99a433 # VITE_THEME_QUATERNARY_CONTENT=#6f7882 # VITE_THEME_QUINARY_CONTENT=#394049 # VITE_THEME_SYSTEM=#21262c diff --git a/src/index.css b/src/index.css index da77b1c5..7ec1e9ca 100644 --- a/src/index.css +++ b/src/index.css @@ -33,6 +33,7 @@ limitations under the License. --primary-content: #ffffff; --secondary-content: #a9b2bc; --tertiary-content: #8e99a4; + --tertiary-content-20: #8e99a433; --quaternary-content: #6f7882; --quinary-content: #394049; --system: #21262c; diff --git a/src/main.tsx b/src/main.tsx index 64d98687..2a643641 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -61,6 +61,10 @@ if (import.meta.env.VITE_CUSTOM_THEME) { "--tertiary-content", import.meta.env.VITE_THEME_TERTIARY_CONTENT as string ); + style.setProperty( + "--tertiary-content-20", + import.meta.env.VITE_THEME_TERTIARY_CONTENT_20 as string + ); style.setProperty( "--quaternary-content", import.meta.env.VITE_THEME_QUATERNARY_CONTENT as string diff --git a/src/room/PTTButton.module.css b/src/room/PTTButton.module.css index fe426a09..8b15d333 100644 --- a/src/room/PTTButton.module.css +++ b/src/room/PTTButton.module.css @@ -17,6 +17,12 @@ cursor: unset; } +.networkWaiting { + background-color: var(--tertiary-content); + border-color: var(--tertiary-content); + cursor: unset; +} + .error { background-color: var(--alert); border-color: var(--alert); diff --git a/src/room/PTTButton.tsx b/src/room/PTTButton.tsx index ef575f64..aed48908 100644 --- a/src/room/PTTButton.tsx +++ b/src/room/PTTButton.tsx @@ -32,12 +32,9 @@ interface Props { size: number; startTalking: () => void; stopTalking: () => void; -} - -interface State { - isHeld: boolean; - // If the button is being pressed by touch, the ID of that touch - activeTouchID: number | null; + networkWaiting: boolean; + enqueueNetworkWaiting: (value: boolean, delay: number) => void; + setNetworkWaiting: (value: boolean) => void; } export const PTTButton: React.FC = ({ @@ -50,19 +47,31 @@ export const PTTButton: React.FC = ({ size, startTalking, stopTalking, + networkWaiting, + enqueueNetworkWaiting, + setNetworkWaiting, }) => { const buttonRef = createRef(); - const [{ isHeld, activeTouchID }, setState] = useState({ - isHeld: false, - activeTouchID: null, - }); + const [held, setHeld] = useState(false); + const [activeTouchId, setActiveTouchId] = useState(null); + + const hold = useCallback(() => { + setHeld(true); + // This update is delayed so the user only sees it if latency is significant + enqueueNetworkWaiting(true, 100); + }, [setHeld, enqueueNetworkWaiting]); + const unhold = useCallback(() => { + setHeld(false); + setNetworkWaiting(false); + }, [setHeld, setNetworkWaiting]); + const onWindowMouseUp = useCallback( (e) => { - if (isHeld) stopTalking(); - setState({ isHeld: false, activeTouchID: null }); + if (held) stopTalking(); + unhold(); }, - [isHeld, setState, stopTalking] + [held, unhold, stopTalking] ); const onWindowTouchEnd = useCallback( @@ -72,7 +81,7 @@ export const PTTButton: React.FC = ({ // have to do this a really old-school way). let touchFound = false; for (let i = 0; i < e.changedTouches.length; ++i) { - if (e.changedTouches.item(i).identifier === activeTouchID) { + if (e.changedTouches.item(i).identifier === activeTouchId) { touchFound = true; break; } @@ -80,34 +89,33 @@ export const PTTButton: React.FC = ({ if (!touchFound) return; e.preventDefault(); - if (isHeld) stopTalking(); - setState({ isHeld: false, activeTouchID: null }); + if (held) stopTalking(); + unhold(); + setActiveTouchId(null); }, - [isHeld, activeTouchID, setState, stopTalking] + [held, activeTouchId, unhold, stopTalking] ); const onButtonMouseDown = useCallback( (e: React.MouseEvent) => { e.preventDefault(); - setState({ isHeld: true, activeTouchID: null }); + hold(); startTalking(); }, - [setState, startTalking] + [hold, startTalking] ); const onButtonTouchStart = useCallback( (e: TouchEvent) => { e.preventDefault(); - if (isHeld) return; - - setState({ - isHeld: true, - activeTouchID: e.changedTouches.item(0).identifier, - }); - startTalking(); + if (!held) { + hold(); + setActiveTouchId(e.changedTouches.item(0).identifier); + startTalking(); + } }, - [isHeld, setState, startTalking] + [held, hold, startTalking] ); useEffect(() => { @@ -143,12 +151,15 @@ export const PTTButton: React.FC = ({ }); const shadowColor = showTalkOverError ? "var(--alert-20)" + : networkWaiting + ? "var(--tertiary-content-20)" : "var(--accent-20)"; return ( = ({ !feedbackModalState.isOpen ); + const [talkingExpected, enqueueTalkingExpected, setTalkingExpected] = + useDelayedState(false); const showTalkOverError = pttButtonHeld && transmitBlocked; + const networkWaiting = + talkingExpected && !activeSpeakerUserId && !showTalkOverError; const activeSpeakerIsLocalUser = activeSpeakerUserId && client.getUserId() === activeSpeakerUserId; @@ -226,9 +236,13 @@ export const PTTCallView: React.FC = ({ size={pttButtonSize} startTalking={startTalking} stopTalking={stopTalking} + networkWaiting={networkWaiting} + enqueueNetworkWaiting={enqueueTalkingExpected} + setNetworkWaiting={setTalkingExpected} />

{getPromptText( + networkWaiting, showTalkOverError, pttButtonHeld, activeSpeakerIsLocalUser,