diff --git a/lib/src/view/fullscreen/fullscreen_view.dart b/lib/src/view/fullscreen/fullscreen_view.dart new file mode 100644 index 0000000..c9baf2c --- /dev/null +++ b/lib/src/view/fullscreen/fullscreen_view.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +/// View displaying a passed widget full screen. +class FullscreenView extends StatefulWidget { + /// Child to display fullscreen. + final Widget _child; + + FullscreenView({ + @required Widget child, + }) : _child = child; + + @override + State createState() => _FullscreenViewState(); +} + +/// State of the fullscreen view. +class _FullscreenViewState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Center(child: widget._child), + ), + ); + } +} diff --git a/lib/src/view/main/main_view.dart b/lib/src/view/main/main_view.dart index 114ff92..0c709a8 100644 --- a/lib/src/view/main/main_view.dart +++ b/lib/src/view/main/main_view.dart @@ -11,6 +11,7 @@ import 'package:bbb_app/src/connect/meeting/main_websocket/video/connection/inco import 'package:bbb_app/src/connect/meeting/meeting_info.dart'; import 'package:bbb_app/src/connect/meeting/model/user_model.dart'; import 'package:bbb_app/src/locale/app_localizations.dart'; +import 'package:bbb_app/src/view/fullscreen/fullscreen_view.dart'; import 'package:bbb_app/src/view/main/presentation/presentation_widget.dart'; import 'package:bbb_app/src/view/meeting_info/meeting_info_view.dart'; import 'package:bbb_app/src/view/privacy_policy/privacy_policy_view.dart'; @@ -206,126 +207,227 @@ class _MainViewState extends State with WidgetsBindingObserver { .forEach((key, value) => _totalUnreadMessages += value); } - @override - Widget build(BuildContext context) { - String screenshareKey; - if (_screenshareVideoConnections.length > 0) { - screenshareKey = _screenshareVideoConnections.keys.elementAt(0); - } + /// Build the button list. + List _buildButtonList() { + return [ + if (!_mainWebSocket.videoModule.isWebcamActive()) + ElevatedButton( + onPressed: () => _toggleWebcamOnOff(context), + child: new Text( + "start webcam", + style: TextStyle(fontSize: 20.0), + ), + ), + if (_mainWebSocket.videoModule.isWebcamActive()) + ElevatedButton( + onPressed: () => _toggleWebcamOnOff(context), + child: new Text( + "stop webcam", + style: TextStyle(fontSize: 20.0), + ), + ), + if (_mainWebSocket.videoModule.isWebcamActive()) + ElevatedButton( + onPressed: () => _toggleWebcamFrontBack(context), + child: new Text( + "switch cam", + style: TextStyle(fontSize: 20.0), + ), + ), + if (!_mainWebSocket.videoModule.isScreenshareActive() && _isPresenter()) + ElevatedButton( + onPressed: () => _toggleScreenshareOnOff(context), + child: new Text( + "start screenshare", + style: TextStyle(fontSize: 20.0), + ), + ), + if (_mainWebSocket.videoModule.isScreenshareActive()) + ElevatedButton( + onPressed: () => _toggleScreenshareOnOff(context), + child: new Text( + "stop screenshare", + style: TextStyle(fontSize: 20.0), + ), + ), + ]; + } - return Scaffold( - appBar: _buildAppBar(), - body: Column( - children: [ - if (_videoConnections.length > 0) - Expanded( - child: PageView.builder( - scrollDirection: Axis.horizontal, - controller: PageController(viewportFraction: 0.6), - itemCount: _videoConnections.length, - itemBuilder: (BuildContext context, int index) { - String key = _videoConnections.keys.elementAt(index); - return Container( - padding: const EdgeInsets.all(8), - width: MediaQuery.of(context).size.width, - child: Stack(children: [ - if (!_videoConnections[key].remoteRenderer.renderVideo) - Center(child: CircularProgressIndicator()), - RTCVideoView(_videoConnections[key].remoteRenderer, - objectFit: RTCVideoViewObjectFit - .RTCVideoViewObjectFitContain), - if (_userMapByInternalId[ - _videoConnections[key].internalUserId] != - null) - Container( - alignment: Alignment.topCenter, - child: Container( - padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white.withOpacity(0.7), - ), - child: Text( - _userMapByInternalId[ - _videoConnections[key].internalUserId] - .name, - style: TextStyle(color: Colors.black), - ), - ), - ) - ]), - ); - }, - ), + /// Build the screen share widget. + Widget _buildScreenShareWidget() { + String screenshareKey = _screenshareVideoConnections.keys.first; + + RTCVideoView videoView = RTCVideoView( + _screenshareVideoConnections[screenshareKey].remoteRenderer, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + ); + + return Container( + padding: const EdgeInsets.all(8), + child: Stack( + children: [ + if (!_screenshareVideoConnections[screenshareKey] + .remoteRenderer + .renderVideo) + Center(child: CircularProgressIndicator()), + videoView, + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: Icon(Icons.fullscreen), + color: Colors.grey, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FullscreenView(child: videoView), + ), + ); + }, ), - if (_screenshareVideoConnections.length == 0) - Expanded( - child: Container( - padding: const EdgeInsets.all(8), - child: PresentationWidget(_mainWebSocket), - )), - if (_screenshareVideoConnections.length > 0) - Expanded( - child: Container( - padding: const EdgeInsets.all(8), - child: Stack( - children: [ - if (!_screenshareVideoConnections[screenshareKey] - .remoteRenderer - .renderVideo) - Center(child: CircularProgressIndicator()), - RTCVideoView( - _screenshareVideoConnections[screenshareKey] - .remoteRenderer, - objectFit: - RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + ), + ], + ), + ); + } + + /// Build the presentation widget to show. + Widget _buildPresentationWidget() { + return Container( + padding: const EdgeInsets.all(8), + child: PresentationWidget(_mainWebSocket), + ); + } + + /// Build the webcam list. + Widget _buildCameraList(Axis axis) { + return PageView.builder( + scrollDirection: axis, + controller: + PageController(viewportFraction: axis == Axis.horizontal ? 0.6 : 0.4), + itemCount: _videoConnections.length, + itemBuilder: (BuildContext context, int index) { + String key = _videoConnections.keys.elementAt(index); + + bool videoShown = _videoConnections[key].remoteRenderer.renderVideo; + + RTCVideoRenderer remoteRenderer = _videoConnections[key].remoteRenderer; + + RTCVideoView videoView = RTCVideoView(remoteRenderer, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain); + + return Container( + margin: const EdgeInsets.all(8), + color: Colors.black87, + child: AspectRatio( + aspectRatio: 4 / 3, + child: Stack( + children: [ + if (!videoShown) Center(child: CircularProgressIndicator()), + videoView, + Container( + margin: EdgeInsets.only(top: 10), + alignment: Alignment.topCenter, + child: Container( + padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white.withOpacity(0.7), + ), + child: Text( + _userMapByInternalId[ + _videoConnections[key].internalUserId] + .name, + style: TextStyle(color: Colors.black), ), - ], + ), ), - ), - ), - if (!_mainWebSocket.videoModule.isWebcamActive()) - ElevatedButton( - onPressed: () => _toggleWebcamOnOff(context), - child: new Text( - "start webcam", - style: TextStyle(fontSize: 20.0), - ), - ), - if (_mainWebSocket.videoModule.isWebcamActive()) - ElevatedButton( - onPressed: () => _toggleWebcamOnOff(context), - child: new Text( - "stop webcam", - style: TextStyle(fontSize: 20.0), - ), - ), - if (_mainWebSocket.videoModule.isWebcamActive()) - ElevatedButton( - onPressed: () => _toggleWebcamFrontBack(context), - child: new Text( - "switch cam", - style: TextStyle(fontSize: 20.0), - ), - ), - if (!_mainWebSocket.videoModule.isScreenshareActive() && - _isPresenter()) - ElevatedButton( - onPressed: () => _toggleScreenshareOnOff(context), - child: new Text( - "start screenshare", - style: TextStyle(fontSize: 20.0), - ), - ), - if (_mainWebSocket.videoModule.isScreenshareActive()) - ElevatedButton( - onPressed: () => _toggleScreenshareOnOff(context), - child: new Text( - "stop screenshare", - style: TextStyle(fontSize: 20.0), - ), + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: Icon(Icons.fullscreen), + color: Colors.grey, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + FullscreenView(child: videoView), + ), + ); + }, + ), + ), + ], ), - ], + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: _buildAppBar(), + body: OrientationBuilder( + builder: (context, orientation) { + if (orientation == Orientation.portrait) { + return Column( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (_videoConnections.length > 0) + SizedBox( + height: 160, + child: _buildCameraList(Axis.horizontal), + ), + if (_screenshareVideoConnections.length == 0) + Expanded( + child: _buildPresentationWidget(), + ), + if (_screenshareVideoConnections.length > 0) + Expanded( + child: _buildScreenShareWidget(), + ), + ], + ), + ), + ..._buildButtonList() + ], + ); + } else { + return Column( + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (_videoConnections.length > 0) + SizedBox( + width: 200, + child: _buildCameraList(Axis.vertical), + ), + if (_screenshareVideoConnections.length == 0) + Expanded( + child: _buildPresentationWidget(), + ), + if (_screenshareVideoConnections.length > 0) + Expanded( + child: _buildScreenShareWidget(), + ), + ], + ), + ), + Row( + children: [..._buildButtonList()], + ), + ], + ); + } + }, ), ); } diff --git a/lib/src/view/main/presentation/presentation_widget.dart b/lib/src/view/main/presentation/presentation_widget.dart index 78e091f..b26c898 100644 --- a/lib/src/view/main/presentation/presentation_widget.dart +++ b/lib/src/view/main/presentation/presentation_widget.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:bbb_app/src/connect/meeting/main_websocket/main_websocket.dart'; import 'package:bbb_app/src/connect/meeting/main_websocket/presentation/model/slide/presentation_slide.dart'; import 'package:bbb_app/src/connect/meeting/main_websocket/presentation/presentation.dart'; +import 'package:bbb_app/src/view/fullscreen/fullscreen_view.dart'; import 'package:bbb_app/src/view/main/presentation/presentation_painter.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -57,7 +58,7 @@ class _PresentationWidgetState extends State { _slideSvg = await svg.fromSvgString(response.body, response.body); //check if this widget is still in tree. (might have been removed from tree during the http.get) - if(mounted) { + if (mounted) { setState(() {}); } } @@ -70,22 +71,52 @@ class _PresentationWidgetState extends State { } @override - Widget build(BuildContext context) => Center( - child: _currentSlide != null && _currentSlide.bounds != null - ? AspectRatio( - aspectRatio: - _currentSlide.bounds.width / _currentSlide.bounds.height, - child: ClipRect( - child: CustomPaint( - painter: PresentationPainter( - _slideSvg, - _currentSlide.bounds, - _currentSlide.annotations.values.toList(growable: false) - ..sort((o1, o2) => o1.position.compareTo(o2.position)), - ), - ), + Widget build(BuildContext context) { + bool hasSlide = _currentSlide != null && _currentSlide.bounds != null; + + if (hasSlide) { + Widget presentation = AspectRatio( + aspectRatio: _currentSlide.bounds.width / _currentSlide.bounds.height, + child: ClipRect( + child: CustomPaint( + painter: PresentationPainter( + _slideSvg, + _currentSlide.bounds, + _currentSlide.annotations.values.toList(growable: false) + ..sort((o1, o2) => o1.position.compareTo(o2.position)), + ), + ), + ), + ); + + return Center( + child: AspectRatio( + aspectRatio: _currentSlide.bounds.width / _currentSlide.bounds.height, + child: Stack( + children: [ + presentation, + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: Icon(Icons.fullscreen), + color: Colors.grey, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + FullscreenView(child: presentation), + ), + ); + }, ), - ) - : const CircularProgressIndicator(), + ), + ], + ), + ), ); + } else { + return Center(child: CircularProgressIndicator()); + } + } }