mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-15 00:04:59 +08:00
Add a 'waiting for network' state to walkie-talkie mode
This commit is contained in:
parent
939398b277
commit
276532e2e1
1
.env
1
.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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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<Props> = ({
|
||||
@ -50,19 +47,30 @@ export const PTTButton: React.FC<Props> = ({
|
||||
size,
|
||||
startTalking,
|
||||
stopTalking,
|
||||
networkWaiting,
|
||||
enqueueNetworkWaiting,
|
||||
setNetworkWaiting,
|
||||
}) => {
|
||||
const buttonRef = createRef<HTMLButtonElement>();
|
||||
|
||||
const [{ isHeld, activeTouchID }, setState] = useState<State>({
|
||||
isHeld: false,
|
||||
activeTouchID: null,
|
||||
});
|
||||
const [held, setHeld] = useState(false);
|
||||
const [activeTouchId, setActiveTouchId] = useState<number | null>(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<Props> = ({
|
||||
// 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<Props> = ({
|
||||
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<HTMLButtonElement>) => {
|
||||
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,
|
||||
});
|
||||
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<Props> = ({
|
||||
});
|
||||
const shadowColor = showTalkOverError
|
||||
? "var(--alert-20)"
|
||||
: networkWaiting
|
||||
? "var(--tertiary-content-20)"
|
||||
: "var(--accent-20)";
|
||||
|
||||
return (
|
||||
<animated.button
|
||||
className={classNames(styles.pttButton, {
|
||||
[styles.talking]: activeSpeakerUserId,
|
||||
[styles.networkWaiting]: networkWaiting,
|
||||
[styles.error]: showTalkOverError,
|
||||
})}
|
||||
style={{
|
||||
|
@ -20,6 +20,7 @@ import { ResizeObserver } from "@juggle/resize-observer";
|
||||
import { GroupCall, MatrixClient, RoomMember } from "matrix-js-sdk";
|
||||
import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
|
||||
|
||||
import { useDelayedState } from "../useDelayedState";
|
||||
import { useModalTriggerState } from "../Modal";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { HangupButton, InviteButton } from "../button";
|
||||
@ -39,6 +40,7 @@ import { GroupCallInspector } from "./GroupCallInspector";
|
||||
import { OverflowMenu } from "./OverflowMenu";
|
||||
|
||||
function getPromptText(
|
||||
networkWaiting: boolean,
|
||||
showTalkOverError: boolean,
|
||||
pttButtonHeld: boolean,
|
||||
activeSpeakerIsLocalUser: boolean,
|
||||
@ -47,10 +49,14 @@ function getPromptText(
|
||||
activeSpeakerDisplayName: string,
|
||||
connected: boolean
|
||||
): string {
|
||||
if (!connected) return "Connection Lost";
|
||||
if (!connected) return "Connection lost";
|
||||
|
||||
const isTouchScreen = Boolean(window.ontouchstart !== undefined);
|
||||
|
||||
if (networkWaiting) {
|
||||
return "Waiting for network";
|
||||
}
|
||||
|
||||
if (showTalkOverError) {
|
||||
return "You can't talk at the same time";
|
||||
}
|
||||
@ -136,7 +142,11 @@ export const PTTCallView: React.FC<Props> = ({
|
||||
!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<Props> = ({
|
||||
size={pttButtonSize}
|
||||
startTalking={startTalking}
|
||||
stopTalking={stopTalking}
|
||||
networkWaiting={networkWaiting}
|
||||
enqueueNetworkWaiting={enqueueTalkingExpected}
|
||||
setNetworkWaiting={setTalkingExpected}
|
||||
/>
|
||||
<p className={styles.actionTip}>
|
||||
{getPromptText(
|
||||
networkWaiting,
|
||||
showTalkOverError,
|
||||
pttButtonHeld,
|
||||
activeSpeakerIsLocalUser,
|
||||
|
44
src/useDelayedState.ts
Normal file
44
src/useDelayedState.ts
Normal file
@ -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 = <T>(
|
||||
initial?: T
|
||||
): [T, (value: T, delay: number) => void, (value: T) => void] => {
|
||||
const [state, setState] = useState<T>(initial);
|
||||
const timers = useRef<Set<ReturnType<typeof setTimeout>>>();
|
||||
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];
|
||||
};
|
Loading…
Reference in New Issue
Block a user