mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-24 00:38:31 +08:00
Merge pull request #2737 from robintown/footer-hide-show
Improve interactions to hide/show the footer
This commit is contained in:
commit
022367ec2e
@ -66,7 +66,6 @@ Please see LICENSE in the repository root for full details.
|
|||||||
.footer.overlay.hidden {
|
.footer.overlay.hidden {
|
||||||
display: grid;
|
display: grid;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer.overlay:has(:focus-visible) {
|
.footer.overlay:has(:focus-visible) {
|
||||||
|
@ -266,12 +266,22 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
}, [vm]);
|
}, [vm]);
|
||||||
const onTouchCancel = useCallback(() => (touchStart.current = null), []);
|
const onTouchCancel = useCallback(() => (touchStart.current = null), []);
|
||||||
|
|
||||||
// We also need to tell the layout toggle to prevent touch events from
|
// We also need to tell the footer controls to prevent touch events from
|
||||||
// bubbling up, or else the controls will be dismissed before a change event
|
// bubbling up, or else the footer will be dismissed before a click/change
|
||||||
// can be registered on the toggle
|
// event can be registered on the control
|
||||||
const onLayoutToggleTouchEnd = useCallback(
|
const onControlsTouchEnd = useCallback(
|
||||||
(e: TouchEvent) => e.stopPropagation(),
|
(e: TouchEvent) => {
|
||||||
[],
|
// Somehow applying pointer-events: none to the controls when the footer
|
||||||
|
// is hidden is not enough to stop clicks from happening as the footer
|
||||||
|
// becomes visible, so we check manually whether the footer is shown
|
||||||
|
if (showFooter) {
|
||||||
|
e.stopPropagation();
|
||||||
|
vm.tapControls();
|
||||||
|
} else {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[vm, showFooter],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onPointerMove = useCallback(
|
const onPointerMove = useCallback(
|
||||||
@ -539,6 +549,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
key="audio"
|
key="audio"
|
||||||
muted={!muteStates.audio.enabled}
|
muted={!muteStates.audio.enabled}
|
||||||
onClick={toggleMicrophone}
|
onClick={toggleMicrophone}
|
||||||
|
onTouchEnd={onControlsTouchEnd}
|
||||||
disabled={muteStates.audio.setEnabled === null}
|
disabled={muteStates.audio.setEnabled === null}
|
||||||
data-testid="incall_mute"
|
data-testid="incall_mute"
|
||||||
/>,
|
/>,
|
||||||
@ -546,6 +557,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
key="video"
|
key="video"
|
||||||
muted={!muteStates.video.enabled}
|
muted={!muteStates.video.enabled}
|
||||||
onClick={toggleCamera}
|
onClick={toggleCamera}
|
||||||
|
onTouchEnd={onControlsTouchEnd}
|
||||||
disabled={muteStates.video.setEnabled === null}
|
disabled={muteStates.video.setEnabled === null}
|
||||||
data-testid="incall_videomute"
|
data-testid="incall_videomute"
|
||||||
/>,
|
/>,
|
||||||
@ -556,6 +568,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
key="switch_camera"
|
key="switch_camera"
|
||||||
className={styles.switchCamera}
|
className={styles.switchCamera}
|
||||||
onClick={switchCamera}
|
onClick={switchCamera}
|
||||||
|
onTouchEnd={onControlsTouchEnd}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
if (canScreenshare && !hideScreensharing) {
|
if (canScreenshare && !hideScreensharing) {
|
||||||
@ -565,6 +578,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
className={styles.shareScreen}
|
className={styles.shareScreen}
|
||||||
enabled={isScreenShareEnabled}
|
enabled={isScreenShareEnabled}
|
||||||
onClick={toggleScreensharing}
|
onClick={toggleScreensharing}
|
||||||
|
onTouchEnd={onControlsTouchEnd}
|
||||||
data-testid="incall_screenshare"
|
data-testid="incall_screenshare"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
@ -576,11 +590,18 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
className={styles.raiseHand}
|
className={styles.raiseHand}
|
||||||
client={client}
|
client={client}
|
||||||
rtcSession={rtcSession}
|
rtcSession={rtcSession}
|
||||||
|
onTouchEnd={onControlsTouchEnd}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (layout.type !== "pip")
|
if (layout.type !== "pip")
|
||||||
buttons.push(<SettingsButton key="settings" onClick={openSettings} />);
|
buttons.push(
|
||||||
|
<SettingsButton
|
||||||
|
key="settings"
|
||||||
|
onClick={openSettings}
|
||||||
|
onTouchEnd={onControlsTouchEnd}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<EndCallButton
|
<EndCallButton
|
||||||
@ -588,6 +609,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
onLeave();
|
onLeave();
|
||||||
}}
|
}}
|
||||||
|
onTouchEnd={onControlsTouchEnd}
|
||||||
data-testid="incall_leave"
|
data-testid="incall_leave"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
@ -615,7 +637,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
className={styles.layout}
|
className={styles.layout}
|
||||||
layout={gridMode}
|
layout={gridMode}
|
||||||
setLayout={setGridMode}
|
setLayout={setGridMode}
|
||||||
onTouchEnd={onLayoutToggleTouchEnd}
|
onTouchEnd={onControlsTouchEnd}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,6 +85,10 @@ const POST_FOCUS_PARTICIPANT_UPDATE_DELAY_MS = 3000;
|
|||||||
// on mobile. No spotlight tile should be shown below this threshold.
|
// on mobile. No spotlight tile should be shown below this threshold.
|
||||||
const smallMobileCallThreshold = 3;
|
const smallMobileCallThreshold = 3;
|
||||||
|
|
||||||
|
// How long the footer should be shown for when hovering over or interacting
|
||||||
|
// with the interface
|
||||||
|
const showFooterMs = 4000;
|
||||||
|
|
||||||
export interface GridLayoutMedia {
|
export interface GridLayoutMedia {
|
||||||
type: "grid";
|
type: "grid";
|
||||||
spotlight?: MediaViewModel[];
|
spotlight?: MediaViewModel[];
|
||||||
@ -902,6 +906,7 @@ export class CallViewModel extends ViewModel {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private readonly screenTap = new Subject<void>();
|
private readonly screenTap = new Subject<void>();
|
||||||
|
private readonly controlsTap = new Subject<void>();
|
||||||
private readonly screenHover = new Subject<void>();
|
private readonly screenHover = new Subject<void>();
|
||||||
private readonly screenUnhover = new Subject<void>();
|
private readonly screenUnhover = new Subject<void>();
|
||||||
|
|
||||||
@ -912,6 +917,13 @@ export class CallViewModel extends ViewModel {
|
|||||||
this.screenTap.next();
|
this.screenTap.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the user taps the call's controls.
|
||||||
|
*/
|
||||||
|
public tapControls(): void {
|
||||||
|
this.controlsTap.next();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for when the user hovers over the call view.
|
* Callback for when the user hovers over the call view.
|
||||||
*/
|
*/
|
||||||
@ -946,27 +958,38 @@ export class CallViewModel extends ViewModel {
|
|||||||
if (isFirefox()) return of(true);
|
if (isFirefox()) return of(true);
|
||||||
// Show/hide the footer in response to interactions
|
// Show/hide the footer in response to interactions
|
||||||
return merge(
|
return merge(
|
||||||
this.screenTap.pipe(map(() => "tap" as const)),
|
this.screenTap.pipe(map(() => "tap screen" as const)),
|
||||||
|
this.controlsTap.pipe(map(() => "tap controls" as const)),
|
||||||
this.screenHover.pipe(map(() => "hover" as const)),
|
this.screenHover.pipe(map(() => "hover" as const)),
|
||||||
).pipe(
|
).pipe(
|
||||||
switchScan(
|
switchScan((state, interaction) => {
|
||||||
(state, interaction) =>
|
switch (interaction) {
|
||||||
interaction === "tap"
|
case "tap screen":
|
||||||
? state
|
return state
|
||||||
? // Toggle visibility on tap
|
? // Toggle visibility on tap
|
||||||
of(false)
|
of(false)
|
||||||
: // Hide after a timeout
|
: // Hide after a timeout
|
||||||
timer(6000).pipe(
|
timer(showFooterMs).pipe(
|
||||||
map(() => false),
|
map(() => false),
|
||||||
startWith(true),
|
startWith(true),
|
||||||
)
|
);
|
||||||
: // Show on hover and hide after a timeout
|
case "tap controls":
|
||||||
race(timer(3000), this.screenUnhover.pipe(take(1))).pipe(
|
// The user is interacting with things, so reset the timeout
|
||||||
map(() => false),
|
return timer(showFooterMs).pipe(
|
||||||
startWith(true),
|
map(() => false),
|
||||||
),
|
startWith(true),
|
||||||
false,
|
);
|
||||||
),
|
case "hover":
|
||||||
|
// Show on hover and hide after a timeout
|
||||||
|
return race(
|
||||||
|
timer(showFooterMs),
|
||||||
|
this.screenUnhover.pipe(take(1)),
|
||||||
|
).pipe(
|
||||||
|
map(() => false),
|
||||||
|
startWith(true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, false),
|
||||||
startWith(false),
|
startWith(false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user