Skip to content

Commit 022367e

Browse files
authored
Merge pull request #2737 from robintown/footer-hide-show
Improve interactions to hide/show the footer
2 parents 56e736b + 68d71a8 commit 022367e

File tree

3 files changed

+67
-23
lines changed

3 files changed

+67
-23
lines changed

src/room/InCallView.module.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ Please see LICENSE in the repository root for full details.
6666
.footer.overlay.hidden {
6767
display: grid;
6868
opacity: 0;
69-
pointer-events: none;
7069
}
7170

7271
.footer.overlay:has(:focus-visible) {

src/room/InCallView.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,22 @@ export const InCallView: FC<InCallViewProps> = ({
266266
}, [vm]);
267267
const onTouchCancel = useCallback(() => (touchStart.current = null), []);
268268

269-
// We also need to tell the layout toggle to prevent touch events from
270-
// bubbling up, or else the controls will be dismissed before a change event
271-
// can be registered on the toggle
272-
const onLayoutToggleTouchEnd = useCallback(
273-
(e: TouchEvent) => e.stopPropagation(),
274-
[],
269+
// We also need to tell the footer controls to prevent touch events from
270+
// bubbling up, or else the footer will be dismissed before a click/change
271+
// event can be registered on the control
272+
const onControlsTouchEnd = useCallback(
273+
(e: TouchEvent) => {
274+
// Somehow applying pointer-events: none to the controls when the footer
275+
// is hidden is not enough to stop clicks from happening as the footer
276+
// becomes visible, so we check manually whether the footer is shown
277+
if (showFooter) {
278+
e.stopPropagation();
279+
vm.tapControls();
280+
} else {
281+
e.preventDefault();
282+
}
283+
},
284+
[vm, showFooter],
275285
);
276286

277287
const onPointerMove = useCallback(
@@ -539,13 +549,15 @@ export const InCallView: FC<InCallViewProps> = ({
539549
key="audio"
540550
muted={!muteStates.audio.enabled}
541551
onClick={toggleMicrophone}
552+
onTouchEnd={onControlsTouchEnd}
542553
disabled={muteStates.audio.setEnabled === null}
543554
data-testid="incall_mute"
544555
/>,
545556
<VideoButton
546557
key="video"
547558
muted={!muteStates.video.enabled}
548559
onClick={toggleCamera}
560+
onTouchEnd={onControlsTouchEnd}
549561
disabled={muteStates.video.setEnabled === null}
550562
data-testid="incall_videomute"
551563
/>,
@@ -556,6 +568,7 @@ export const InCallView: FC<InCallViewProps> = ({
556568
key="switch_camera"
557569
className={styles.switchCamera}
558570
onClick={switchCamera}
571+
onTouchEnd={onControlsTouchEnd}
559572
/>,
560573
);
561574
if (canScreenshare && !hideScreensharing) {
@@ -565,6 +578,7 @@ export const InCallView: FC<InCallViewProps> = ({
565578
className={styles.shareScreen}
566579
enabled={isScreenShareEnabled}
567580
onClick={toggleScreensharing}
581+
onTouchEnd={onControlsTouchEnd}
568582
data-testid="incall_screenshare"
569583
/>,
570584
);
@@ -576,18 +590,26 @@ export const InCallView: FC<InCallViewProps> = ({
576590
className={styles.raiseHand}
577591
client={client}
578592
rtcSession={rtcSession}
593+
onTouchEnd={onControlsTouchEnd}
579594
/>,
580595
);
581596
}
582597
if (layout.type !== "pip")
583-
buttons.push(<SettingsButton key="settings" onClick={openSettings} />);
598+
buttons.push(
599+
<SettingsButton
600+
key="settings"
601+
onClick={openSettings}
602+
onTouchEnd={onControlsTouchEnd}
603+
/>,
604+
);
584605

585606
buttons.push(
586607
<EndCallButton
587608
key="end_call"
588609
onClick={function (): void {
589610
onLeave();
590611
}}
612+
onTouchEnd={onControlsTouchEnd}
591613
data-testid="incall_leave"
592614
/>,
593615
);
@@ -615,7 +637,7 @@ export const InCallView: FC<InCallViewProps> = ({
615637
className={styles.layout}
616638
layout={gridMode}
617639
setLayout={setGridMode}
618-
onTouchEnd={onLayoutToggleTouchEnd}
640+
onTouchEnd={onControlsTouchEnd}
619641
/>
620642
)}
621643
</div>

src/state/CallViewModel.ts

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ const POST_FOCUS_PARTICIPANT_UPDATE_DELAY_MS = 3000;
8585
// on mobile. No spotlight tile should be shown below this threshold.
8686
const smallMobileCallThreshold = 3;
8787

88+
// How long the footer should be shown for when hovering over or interacting
89+
// with the interface
90+
const showFooterMs = 4000;
91+
8892
export interface GridLayoutMedia {
8993
type: "grid";
9094
spotlight?: MediaViewModel[];
@@ -902,6 +906,7 @@ export class CallViewModel extends ViewModel {
902906
);
903907

904908
private readonly screenTap = new Subject<void>();
909+
private readonly controlsTap = new Subject<void>();
905910
private readonly screenHover = new Subject<void>();
906911
private readonly screenUnhover = new Subject<void>();
907912

@@ -912,6 +917,13 @@ export class CallViewModel extends ViewModel {
912917
this.screenTap.next();
913918
}
914919

920+
/**
921+
* Callback for when the user taps the call's controls.
922+
*/
923+
public tapControls(): void {
924+
this.controlsTap.next();
925+
}
926+
915927
/**
916928
* Callback for when the user hovers over the call view.
917929
*/
@@ -946,27 +958,38 @@ export class CallViewModel extends ViewModel {
946958
if (isFirefox()) return of(true);
947959
// Show/hide the footer in response to interactions
948960
return merge(
949-
this.screenTap.pipe(map(() => "tap" as const)),
961+
this.screenTap.pipe(map(() => "tap screen" as const)),
962+
this.controlsTap.pipe(map(() => "tap controls" as const)),
950963
this.screenHover.pipe(map(() => "hover" as const)),
951964
).pipe(
952-
switchScan(
953-
(state, interaction) =>
954-
interaction === "tap"
955-
? state
965+
switchScan((state, interaction) => {
966+
switch (interaction) {
967+
case "tap screen":
968+
return state
956969
? // Toggle visibility on tap
957970
of(false)
958971
: // Hide after a timeout
959-
timer(6000).pipe(
972+
timer(showFooterMs).pipe(
960973
map(() => false),
961974
startWith(true),
962-
)
963-
: // Show on hover and hide after a timeout
964-
race(timer(3000), this.screenUnhover.pipe(take(1))).pipe(
965-
map(() => false),
966-
startWith(true),
967-
),
968-
false,
969-
),
975+
);
976+
case "tap controls":
977+
// The user is interacting with things, so reset the timeout
978+
return timer(showFooterMs).pipe(
979+
map(() => false),
980+
startWith(true),
981+
);
982+
case "hover":
983+
// Show on hover and hide after a timeout
984+
return race(
985+
timer(showFooterMs),
986+
this.screenUnhover.pipe(take(1)),
987+
).pipe(
988+
map(() => false),
989+
startWith(true),
990+
);
991+
}
992+
}, false),
970993
startWith(false),
971994
);
972995
}

0 commit comments

Comments
 (0)