feat(mobile): add swipe navigation and tileview button for mobile layout switching
Add Zoom-like layout switching on mobile web: horizontal swipe on video area toggles between speaker and tile view, tileview button now visible in toolbar at mobile widths (≤470px), and layout indicator dots show current view mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
65c4a1acf0
commit
57db6da004
|
|
@ -28,6 +28,32 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.layout-indicator {
|
||||
bottom: 80px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.layout-indicator-dot {
|
||||
border-radius: 50%;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.layout-indicator-dot--active {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.layout-indicator-dot--inactive {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import { toggleToolboxVisible } from '../../../toolbox/actions.any';
|
|||
import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
|
||||
import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
|
||||
import Toolbox from '../../../toolbox/components/web/Toolbox';
|
||||
import { toggleTileView } from '../../../video-layout/actions.any';
|
||||
import { LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
|
||||
import { getCurrentLayout } from '../../../video-layout/functions.any';
|
||||
import VisitorsQueue from '../../../visitors/components/web/VisitorsQueue';
|
||||
|
|
@ -137,6 +138,9 @@ function shouldShowPrejoin({ _showLobby, _showPrejoin, _showVisitorsQueue }: IPr
|
|||
class Conference extends AbstractConference<IProps, any> {
|
||||
_originalOnMouseMove: Function;
|
||||
_originalOnShowToolbar: Function;
|
||||
_touchStartX = 0;
|
||||
_touchStartY = 0;
|
||||
_touchStartTime = 0;
|
||||
|
||||
/**
|
||||
* Initializes a new Conference instance.
|
||||
|
|
@ -173,6 +177,7 @@ class Conference extends AbstractConference<IProps, any> {
|
|||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onFullScreenChange = this._onFullScreenChange.bind(this);
|
||||
this._onVideospaceTouchStart = this._onVideospaceTouchStart.bind(this);
|
||||
this._onVideospaceTouchEnd = this._onVideospaceTouchEnd.bind(this);
|
||||
this._setBackground = this._setBackground.bind(this);
|
||||
}
|
||||
|
||||
|
|
@ -263,6 +268,7 @@ class Conference extends AbstractConference<IProps, any> {
|
|||
<Notice />
|
||||
<div
|
||||
id = 'videospace'
|
||||
onTouchEnd = { this._onVideospaceTouchEnd }
|
||||
onTouchStart = { this._onVideospaceTouchStart }>
|
||||
<LargeVideo />
|
||||
</div>
|
||||
|
|
@ -294,6 +300,7 @@ class Conference extends AbstractConference<IProps, any> {
|
|||
<Notice />
|
||||
<div
|
||||
id = 'videospace'
|
||||
onTouchEnd = { this._onVideospaceTouchEnd }
|
||||
onTouchStart = { this._onVideospaceTouchStart }>
|
||||
<LargeVideo />
|
||||
{
|
||||
|
|
@ -303,6 +310,20 @@ class Conference extends AbstractConference<IProps, any> {
|
|||
<MainFilmstrip />
|
||||
</>)
|
||||
}
|
||||
{ isMobileBrowser() && (
|
||||
<div className = 'layout-indicator'>
|
||||
<div
|
||||
className = { `layout-indicator-dot ${
|
||||
this.props._shouldDisplayTileView
|
||||
? 'layout-indicator-dot--inactive'
|
||||
: 'layout-indicator-dot--active'}` } />
|
||||
<div
|
||||
className = { `layout-indicator-dot ${
|
||||
this.props._shouldDisplayTileView
|
||||
? 'layout-indicator-dot--active'
|
||||
: 'layout-indicator-dot--inactive'}` } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{ _showPrejoin || _showLobby || (
|
||||
|
|
@ -368,12 +389,50 @@ class Conference extends AbstractConference<IProps, any> {
|
|||
|
||||
/**
|
||||
* Handler used for touch start on Video container.
|
||||
* Records touch coordinates for swipe detection on mobile.
|
||||
*
|
||||
* @param {TouchEvent} event - The touch event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onVideospaceTouchStart() {
|
||||
this.props.dispatch(toggleToolboxVisible());
|
||||
_onVideospaceTouchStart(event: React.TouchEvent) {
|
||||
if (event.touches.length === 1) {
|
||||
this._touchStartX = event.touches[0].clientX;
|
||||
this._touchStartY = event.touches[0].clientY;
|
||||
this._touchStartTime = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler used for touch end on Video container.
|
||||
* Detects horizontal swipe to toggle tile view, or short tap to toggle toolbox.
|
||||
*
|
||||
* @param {TouchEvent} event - The touch event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onVideospaceTouchEnd(event: React.TouchEvent) {
|
||||
if (event.changedTouches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dx = event.changedTouches[0].clientX - this._touchStartX;
|
||||
const dy = event.changedTouches[0].clientY - this._touchStartY;
|
||||
const absDx = Math.abs(dx);
|
||||
const absDy = Math.abs(dy);
|
||||
const elapsed = Date.now() - this._touchStartTime;
|
||||
|
||||
// Horizontal swipe: >=60px horizontal, more horizontal than vertical, within 500ms.
|
||||
if (absDx >= 60 && absDx > absDy * 1.5 && elapsed < 500) {
|
||||
this.props.dispatch(toggleTileView());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Short tap: <300ms, <10px movement.
|
||||
if (elapsed < 300 && absDx < 10 && absDy < 10) {
|
||||
this.props.dispatch(toggleToolboxVisible());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -40,19 +40,19 @@ export const THRESHOLDS = [
|
|||
},
|
||||
{
|
||||
width: 470,
|
||||
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane' ]
|
||||
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'tileview' ]
|
||||
},
|
||||
{
|
||||
width: 420,
|
||||
order: [ 'microphone', 'camera', 'desktop', 'chat', 'participants-pane' ]
|
||||
order: [ 'microphone', 'camera', 'desktop', 'chat', 'tileview' ]
|
||||
},
|
||||
{
|
||||
width: 370,
|
||||
order: [ 'microphone', 'camera', 'chat', 'participants-pane' ]
|
||||
order: [ 'microphone', 'camera', 'chat', 'tileview' ]
|
||||
},
|
||||
{
|
||||
width: 225,
|
||||
order: [ 'microphone', 'camera', 'chat' ]
|
||||
order: [ 'microphone', 'camera', 'tileview' ]
|
||||
},
|
||||
{
|
||||
width: 200,
|
||||
|
|
|
|||
Loading…
Reference in New Issue