|
|
|
import 'dart:async';
|
|
|
|
|
|
|
|
import 'package:diplomaticquarterapp/models/LiveCare/room_model.dart';
|
|
|
|
import 'package:diplomaticquarterapp/pages/conference/conference_button_bar.dart';
|
|
|
|
import 'package:diplomaticquarterapp/pages/conference/conference_room.dart';
|
|
|
|
import 'package:diplomaticquarterapp/pages/conference/draggable_publisher.dart';
|
|
|
|
import 'package:diplomaticquarterapp/pages/conference/participant_widget.dart';
|
|
|
|
import 'package:diplomaticquarterapp/pages/conference/widgets/noise_box.dart';
|
|
|
|
import 'package:diplomaticquarterapp/pages/conference/widgets/platform_alert_dialog.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:wakelock/wakelock.dart';
|
|
|
|
|
|
|
|
class ConferencePage extends StatefulWidget {
|
|
|
|
final RoomModel roomModel;
|
|
|
|
|
|
|
|
const ConferencePage({Key key, this.roomModel}) : super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
_ConferencePageState createState() => _ConferencePageState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ConferencePageState extends State<ConferencePage> {
|
|
|
|
final StreamController<bool> _onButtonBarVisibleStreamController = StreamController<bool>.broadcast();
|
|
|
|
final StreamController<double> _onButtonBarHeightStreamController = StreamController<double>.broadcast();
|
|
|
|
ConferenceRoom _conferenceRoom;
|
|
|
|
StreamSubscription _onConferenceRoomException;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_lockInPortrait();
|
|
|
|
_connectToRoom();
|
|
|
|
_wakeLock(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _connectToRoom() async {
|
|
|
|
try {
|
|
|
|
final conferenceRoom = ConferenceRoom(
|
|
|
|
name: widget.roomModel.name,
|
|
|
|
token: widget.roomModel.token,
|
|
|
|
identity: widget.roomModel.identity,
|
|
|
|
);
|
|
|
|
await conferenceRoom.connect();
|
|
|
|
setState(() {
|
|
|
|
_conferenceRoom = conferenceRoom;
|
|
|
|
_onConferenceRoomException = _conferenceRoom.onException.listen((err) async {
|
|
|
|
await PlatformAlertDialog(
|
|
|
|
title: err is PlatformException ? err.message : 'An error occured',
|
|
|
|
content: err is PlatformException ? err.details : err.toString(),
|
|
|
|
defaultActionText: 'OK',
|
|
|
|
).show(context);
|
|
|
|
});
|
|
|
|
_conferenceRoom.addListener(_conferenceRoomUpdated);
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
print(err);
|
|
|
|
await PlatformAlertDialog(
|
|
|
|
title: err is PlatformException ? err.message : 'An error occured',
|
|
|
|
content: err is PlatformException ? err.details : err.toString(),
|
|
|
|
defaultActionText: 'OK',
|
|
|
|
).show(context);
|
|
|
|
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _lockInPortrait() async {
|
|
|
|
await SystemChrome.setPreferredOrientations(<DeviceOrientation>[
|
|
|
|
DeviceOrientation.portraitUp,
|
|
|
|
DeviceOrientation.portraitDown,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_freePortraitLock();
|
|
|
|
_wakeLock(false);
|
|
|
|
_disposeStreamsAndSubscriptions();
|
|
|
|
if (_conferenceRoom != null) _conferenceRoom.removeListener(_conferenceRoomUpdated);
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _freePortraitLock() async {
|
|
|
|
await SystemChrome.setPreferredOrientations(<DeviceOrientation>[
|
|
|
|
DeviceOrientation.landscapeRight,
|
|
|
|
DeviceOrientation.landscapeLeft,
|
|
|
|
DeviceOrientation.portraitUp,
|
|
|
|
DeviceOrientation.portraitDown,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _disposeStreamsAndSubscriptions() async {
|
|
|
|
if (_onButtonBarVisibleStreamController != null) await _onButtonBarVisibleStreamController.close();
|
|
|
|
if (_onButtonBarHeightStreamController != null) await _onButtonBarHeightStreamController.close();
|
|
|
|
if (_onConferenceRoomException != null) await _onConferenceRoomException.cancel();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return WillPopScope(
|
|
|
|
onWillPop: () async => false,
|
|
|
|
child: Scaffold(
|
|
|
|
backgroundColor: Colors.black,
|
|
|
|
body: _conferenceRoom == null ? showProgress() : buildLayout(),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
LayoutBuilder buildLayout() {
|
|
|
|
return LayoutBuilder(
|
|
|
|
builder: (BuildContext context, BoxConstraints constraints) {
|
|
|
|
return Stack(
|
|
|
|
children: <Widget>[
|
|
|
|
_buildParticipants(context, constraints.biggest, _conferenceRoom),
|
|
|
|
ConferenceButtonBar(
|
|
|
|
audioEnabled: _conferenceRoom.onAudioEnabled,
|
|
|
|
videoEnabled: _conferenceRoom.onVideoEnabled,
|
|
|
|
onAudioEnabled: _conferenceRoom.toggleAudioEnabled,
|
|
|
|
onVideoEnabled: _conferenceRoom.toggleVideoEnabled,
|
|
|
|
onHangup: _onHangup,
|
|
|
|
onSwitchCamera: _conferenceRoom.switchCamera,
|
|
|
|
onPersonAdd: _onPersonAdd,
|
|
|
|
onPersonRemove: _onPersonRemove,
|
|
|
|
onHeight: _onHeightBar,
|
|
|
|
onShow: _onShowBar,
|
|
|
|
onHide: _onHideBar,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget showProgress() {
|
|
|
|
return Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
children: <Widget>[
|
|
|
|
Center(child: CircularProgressIndicator()),
|
|
|
|
SizedBox(
|
|
|
|
height: 10,
|
|
|
|
),
|
|
|
|
Text(
|
|
|
|
'Connecting to the call...',
|
|
|
|
style: TextStyle(color: Colors.white),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _onHangup() async {
|
|
|
|
print('onHangup');
|
|
|
|
await _conferenceRoom.disconnect();
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onPersonAdd() {
|
|
|
|
print('onPersonAdd');
|
|
|
|
try {
|
|
|
|
_conferenceRoom.addDummy(
|
|
|
|
child: Stack(
|
|
|
|
children: <Widget>[
|
|
|
|
const Placeholder(),
|
|
|
|
Center(
|
|
|
|
child: Text(
|
|
|
|
(_conferenceRoom.participants.length + 1).toString(),
|
|
|
|
style: const TextStyle(
|
|
|
|
shadows: <Shadow>[
|
|
|
|
Shadow(
|
|
|
|
blurRadius: 3.0,
|
|
|
|
color: Color.fromARGB(255, 0, 0, 0),
|
|
|
|
),
|
|
|
|
Shadow(
|
|
|
|
blurRadius: 8.0,
|
|
|
|
color: Color.fromARGB(255, 255, 255, 255),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
fontSize: 80,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} on PlatformException catch (err) {
|
|
|
|
PlatformAlertDialog(
|
|
|
|
title: err.message,
|
|
|
|
content: err.details,
|
|
|
|
defaultActionText: 'OK',
|
|
|
|
).show(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onPersonRemove() {
|
|
|
|
print('onPersonRemove');
|
|
|
|
_conferenceRoom.removeDummy();
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _buildParticipants(BuildContext context, Size size, ConferenceRoom conferenceRoom) {
|
|
|
|
final children = <Widget>[];
|
|
|
|
final length = conferenceRoom.participants.length;
|
|
|
|
|
|
|
|
if (length <= 2) {
|
|
|
|
_buildOverlayLayout(context, size, children);
|
|
|
|
return Stack(children: children);
|
|
|
|
}
|
|
|
|
|
|
|
|
void buildInCols(bool removeLocalBeforeChunking, bool moveLastOfEachRowToNextRow, int columns) {
|
|
|
|
_buildLayoutInGrid(
|
|
|
|
context,
|
|
|
|
size,
|
|
|
|
children,
|
|
|
|
removeLocalBeforeChunking: removeLocalBeforeChunking,
|
|
|
|
moveLastOfEachRowToNextRow: moveLastOfEachRowToNextRow,
|
|
|
|
columns: columns,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if (length <= 3) {
|
|
|
|
// buildInCols(true, false, 1);
|
|
|
|
// } else if (length == 5) {
|
|
|
|
// buildInCols(false, true, 2);
|
|
|
|
// } else if (length <= 6 || length == 8) {
|
|
|
|
// buildInCols(false, false, 2);
|
|
|
|
// } else if (length == 7 || length == 9) {
|
|
|
|
// buildInCols(true, false, 2);
|
|
|
|
// } else if (length == 10) {
|
|
|
|
// buildInCols(false, true, 3);
|
|
|
|
// } else if (length == 13 || length == 16) {
|
|
|
|
// buildInCols(true, false, 3);
|
|
|
|
// } else if (length <= 18) {
|
|
|
|
// buildInCols(false, false, 3);
|
|
|
|
// }
|
|
|
|
|
|
|
|
return Column(
|
|
|
|
children: children,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _buildOverlayLayout(BuildContext context, Size size, List<Widget> children) {
|
|
|
|
final participants = _conferenceRoom.participants;
|
|
|
|
if (participants.length == 1) {
|
|
|
|
children.add(_buildNoiseBox());
|
|
|
|
} else {
|
|
|
|
final remoteParticipant = participants.firstWhere((ParticipantWidget participant) => participant.isRemote, orElse: () => null);
|
|
|
|
if (remoteParticipant != null) {
|
|
|
|
children.add(remoteParticipant);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final localParticipant = participants.firstWhere((ParticipantWidget participant) => !participant.isRemote, orElse: () => null);
|
|
|
|
if (localParticipant != null) {
|
|
|
|
children.add(DraggablePublisher(
|
|
|
|
key: Key('publisher'),
|
|
|
|
child: localParticipant,
|
|
|
|
availableScreenSize: size,
|
|
|
|
onButtonBarVisible: _onButtonBarVisibleStreamController.stream,
|
|
|
|
onButtonBarHeight: _onButtonBarHeightStreamController.stream,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _buildLayoutInGrid(
|
|
|
|
BuildContext context,
|
|
|
|
Size size,
|
|
|
|
List<Widget> children, {
|
|
|
|
bool removeLocalBeforeChunking = false,
|
|
|
|
bool moveLastOfEachRowToNextRow = false,
|
|
|
|
int columns = 2,
|
|
|
|
}) {
|
|
|
|
final participants = _conferenceRoom.participants;
|
|
|
|
ParticipantWidget localParticipant;
|
|
|
|
if (removeLocalBeforeChunking) {
|
|
|
|
localParticipant = participants.firstWhere((ParticipantWidget participant) => !participant.isRemote, orElse: () => null);
|
|
|
|
if (localParticipant != null) {
|
|
|
|
participants.remove(localParticipant);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
final chunkedParticipants = chunk(array: participants, size: columns);
|
|
|
|
if (localParticipant != null) {
|
|
|
|
chunkedParticipants.last.add(localParticipant);
|
|
|
|
participants.add(localParticipant);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (moveLastOfEachRowToNextRow) {
|
|
|
|
for (var i = 0; i < chunkedParticipants.length - 1; i++) {
|
|
|
|
var participant = chunkedParticipants[i].removeLast();
|
|
|
|
chunkedParticipants[i + 1].insert(0, participant);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (final participantChunk in chunkedParticipants) {
|
|
|
|
final rowChildren = <Widget>[];
|
|
|
|
for (final participant in participantChunk) {
|
|
|
|
rowChildren.add(
|
|
|
|
Container(
|
|
|
|
width: size.width / participantChunk.length,
|
|
|
|
height: size.height / chunkedParticipants.length,
|
|
|
|
child: participant,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
children.add(
|
|
|
|
Container(
|
|
|
|
height: size.height / chunkedParticipants.length,
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
|
|
children: rowChildren,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NoiseBox _buildNoiseBox() {
|
|
|
|
return NoiseBox(
|
|
|
|
density: NoiseBoxDensity.xLow,
|
|
|
|
backgroundColor: Colors.grey.shade900,
|
|
|
|
child: Center(
|
|
|
|
child: Container(
|
|
|
|
color: Colors.black54,
|
|
|
|
width: double.infinity,
|
|
|
|
height: 40,
|
|
|
|
child: Center(
|
|
|
|
child: Text(
|
|
|
|
'Waiting for another participant to connect to the call...',
|
|
|
|
key: Key('text-wait'),
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
style: TextStyle(color: Colors.white),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<List<T>> chunk<T>({@required List<T> array, @required int size}) {
|
|
|
|
final result = <List<T>>[];
|
|
|
|
if (array.isEmpty || size <= 0) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
var first = 0;
|
|
|
|
var last = size;
|
|
|
|
final totalLoop = array.length % size == 0 ? array.length ~/ size : array.length ~/ size + 1;
|
|
|
|
for (var i = 0; i < totalLoop; i++) {
|
|
|
|
if (last > array.length) {
|
|
|
|
result.add(array.sublist(first, array.length));
|
|
|
|
} else {
|
|
|
|
result.add(array.sublist(first, last));
|
|
|
|
}
|
|
|
|
first = last;
|
|
|
|
last = last + size;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onHeightBar(double height) {
|
|
|
|
_onButtonBarHeightStreamController.add(height);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onShowBar() {
|
|
|
|
setState(() {
|
|
|
|
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]);
|
|
|
|
});
|
|
|
|
_onButtonBarVisibleStreamController.add(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onHideBar() {
|
|
|
|
setState(() {
|
|
|
|
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
|
|
|
|
});
|
|
|
|
_onButtonBarVisibleStreamController.add(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _wakeLock(bool enable) async {
|
|
|
|
try {
|
|
|
|
return await (enable ? Wakelock.enable() : Wakelock.disable());
|
|
|
|
} catch (err) {
|
|
|
|
print('Unable to change the Wakelock and set it to $enable');
|
|
|
|
print(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _conferenceRoomUpdated() {
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
}
|