From 276532e2e1640e5bc343695a699d9438c44d9123 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 14 Jun 2022 12:00:26 -0400 Subject: [PATCH] Add a 'waiting for network' state to walkie-talkie mode --- .env | 1 + src/index.css | 1 + src/main.tsx | 4 +++ src/room/PTTButton.module.css | 6 ++++ src/room/PTTButton.tsx | 64 ++++++++++++++++++++--------------- src/room/PTTCallView.tsx | 16 ++++++++- src/useDelayedState.ts | 44 ++++++++++++++++++++++++ 7 files changed, 108 insertions(+), 28 deletions(-) create mode 100644 src/useDelayedState.ts 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..c52326e7 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,30 @@ 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); + 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 +80,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 +88,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 +150,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, diff --git a/src/useDelayedState.ts b/src/useDelayedState.ts new file mode 100644 index 00000000..caff435d --- /dev/null +++ b/src/useDelayedState.ts @@ -0,0 +1,44 @@ +/* +Copyright 2022 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { useState, useRef, useEffect } from "react"; + +export const useDelayedState = ( + initial?: T +): [T, (value: T, delay: number) => void, (value: T) => void] => { + const [state, setState] = useState(initial); + const timers = useRef>>(); + if (!timers.current) timers.current = new Set(); + + const setStateDelayed = (value: T, delay: number) => { + const timer = setTimeout(() => { + setState(value); + timers.current.delete(timer); + }, delay); + timers.current.add(timer); + }; + const setStateImmediate = (value: T) => { + // Clear all updates currently in the queue + for (const timer of timers.current) clearTimeout(timer); + timers.current.clear(); + + setState(value); + }; + + useEffect(() => console.log("got", state), [state]); + + return [state, setStateDelayed, setStateImmediate]; +};