diff --git a/css/_videolayout_default.scss b/css/_videolayout_default.scss index e1bfd3f..9113b24 100644 --- a/css/_videolayout_default.scss +++ b/css/_videolayout_default.scss @@ -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; diff --git a/react/features/conference/components/web/Conference.tsx b/react/features/conference/components/web/Conference.tsx index c5d504c..5076602 100644 --- a/react/features/conference/components/web/Conference.tsx +++ b/react/features/conference/components/web/Conference.tsx @@ -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 { _originalOnMouseMove: Function; _originalOnShowToolbar: Function; + _touchStartX = 0; + _touchStartY = 0; + _touchStartTime = 0; /** * Initializes a new Conference instance. @@ -173,6 +177,7 @@ class Conference extends AbstractConference { // 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 {
@@ -294,6 +300,7 @@ class Conference extends AbstractConference {
{ @@ -303,6 +310,20 @@ class Conference extends AbstractConference { ) } + { isMobileBrowser() && ( +
+
+
+
+ )}
{ _showPrejoin || _showLobby || ( @@ -368,12 +389,50 @@ class Conference extends AbstractConference { /** * 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()); + } } /** diff --git a/react/features/toolbox/constants.ts b/react/features/toolbox/constants.ts index 4ed35f9..b8c2624 100644 --- a/react/features/toolbox/constants.ts +++ b/react/features/toolbox/constants.ts @@ -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,