Merge pull request #326 from vector-im/dbkr/ptt_button_touch_fixes
Fixes for touch interface on push-to-talk button
This commit is contained in:
commit
2a19a9964d
1 changed files with 56 additions and 13 deletions
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState, createRef } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import styles from "./PTTButton.module.css";
|
import styles from "./PTTButton.module.css";
|
||||||
|
@ -32,6 +32,12 @@ interface Props {
|
||||||
stopTalking: () => void;
|
stopTalking: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
isHeld: boolean;
|
||||||
|
// If the button is being pressed by touch, the ID of that touch
|
||||||
|
activeTouchID: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
export const PTTButton: React.FC<Props> = ({
|
export const PTTButton: React.FC<Props> = ({
|
||||||
showTalkOverError,
|
showTalkOverError,
|
||||||
activeSpeakerUserId,
|
activeSpeakerUserId,
|
||||||
|
@ -42,51 +48,88 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
startTalking,
|
startTalking,
|
||||||
stopTalking,
|
stopTalking,
|
||||||
}) => {
|
}) => {
|
||||||
const [isHeld, setHeld] = useState(false);
|
const buttonRef = createRef<HTMLButtonElement>();
|
||||||
|
|
||||||
|
const [{ isHeld, activeTouchID }, setState] = useState<State>({
|
||||||
|
isHeld: false,
|
||||||
|
activeTouchID: null,
|
||||||
|
});
|
||||||
const onWindowMouseUp = useCallback(
|
const onWindowMouseUp = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (isHeld) stopTalking();
|
if (isHeld) stopTalking();
|
||||||
setHeld(false);
|
setState({ isHeld: false, activeTouchID: null });
|
||||||
},
|
},
|
||||||
[isHeld, setHeld, stopTalking]
|
[isHeld, setState, stopTalking]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onWindowTouchEnd = useCallback(
|
const onWindowTouchEnd = useCallback(
|
||||||
(e: TouchEvent) => {
|
(e: TouchEvent) => {
|
||||||
|
// ignore any ended touches that weren't the one pressing the
|
||||||
|
// button (bafflingly the TouchList isn't an iterable so we
|
||||||
|
// 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) {
|
||||||
|
touchFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!touchFound) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (isHeld) stopTalking();
|
if (isHeld) stopTalking();
|
||||||
setHeld(false);
|
setState({ isHeld: false, activeTouchID: null });
|
||||||
},
|
},
|
||||||
[isHeld, setHeld, stopTalking]
|
[isHeld, activeTouchID, setState, stopTalking]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onButtonMouseDown = useCallback(
|
const onButtonMouseDown = useCallback(
|
||||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setHeld(true);
|
setState({ isHeld: true, activeTouchID: null });
|
||||||
startTalking();
|
startTalking();
|
||||||
},
|
},
|
||||||
[setHeld, startTalking]
|
[setState, startTalking]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onButtonTouchStart = useCallback(
|
const onButtonTouchStart = useCallback(
|
||||||
(e: React.TouchEvent<HTMLButtonElement>) => {
|
(e: TouchEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setHeld(true);
|
|
||||||
|
if (isHeld) return;
|
||||||
|
|
||||||
|
setState({
|
||||||
|
isHeld: true,
|
||||||
|
activeTouchID: e.changedTouches.item(0).identifier,
|
||||||
|
});
|
||||||
startTalking();
|
startTalking();
|
||||||
},
|
},
|
||||||
[setHeld, startTalking]
|
[isHeld, setState, startTalking]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const currentButtonElement = buttonRef.current;
|
||||||
|
|
||||||
|
// These listeners go on the window so even if the user's cursor / finger
|
||||||
|
// leaves the button while holding it, the button stays pushed until
|
||||||
|
// they stop clicking / tapping.
|
||||||
window.addEventListener("mouseup", onWindowMouseUp);
|
window.addEventListener("mouseup", onWindowMouseUp);
|
||||||
window.addEventListener("touchend", onWindowTouchEnd);
|
window.addEventListener("touchend", onWindowTouchEnd);
|
||||||
|
// This is a native DOM listener too because we want to preventDefault in it
|
||||||
|
// to stop also getting a click event, so we need it to be non-passive.
|
||||||
|
currentButtonElement.addEventListener("touchstart", onButtonTouchStart, {
|
||||||
|
passive: false,
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("mouseup", onWindowMouseUp);
|
window.removeEventListener("mouseup", onWindowMouseUp);
|
||||||
window.removeEventListener("touchend", onWindowTouchEnd);
|
window.removeEventListener("touchend", onWindowTouchEnd);
|
||||||
|
currentButtonElement.removeEventListener(
|
||||||
|
"touchstart",
|
||||||
|
onButtonTouchStart
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, [onWindowMouseUp, onWindowTouchEnd]);
|
}, [onWindowMouseUp, onWindowTouchEnd, onButtonTouchStart, buttonRef]);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classNames(styles.pttButton, {
|
className={classNames(styles.pttButton, {
|
||||||
|
@ -94,7 +137,7 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
[styles.error]: showTalkOverError,
|
[styles.error]: showTalkOverError,
|
||||||
})}
|
})}
|
||||||
onMouseDown={onButtonMouseDown}
|
onMouseDown={onButtonMouseDown}
|
||||||
onTouchStart={onButtonTouchStart}
|
ref={buttonRef}
|
||||||
>
|
>
|
||||||
{activeSpeakerIsLocalUser || !activeSpeakerUserId ? (
|
{activeSpeakerIsLocalUser || !activeSpeakerUserId ? (
|
||||||
<MicIcon
|
<MicIcon
|
||||||
|
|
Loading…
Add table
Reference in a new issue