implementing Twilio in LiveCare

merge-requests/34/merge
haroon amjad 5 years ago
parent db30fbdfd2
commit 5f1413a2ac

@ -1,4 +1,5 @@
import 'package:diplomaticquarterapp/routes.dart';
import 'package:diplomaticquarterapp/uitl/navigation_service.dart';
import 'package:get_it/get_it.dart';
import 'core/service/feedback/feedback_service.dart';
@ -37,6 +38,7 @@ void setupLocator() {
locator.registerLazySingleton(() => InsuranceCardService());
locator.registerLazySingleton(() => VitalSignService());
locator.registerLazySingleton(() => MedicalService());
locator.registerLazySingleton(() => NavigationService());
/// View Model
locator.registerFactory(() => HospitalViewModel());

@ -1,4 +1,7 @@
import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart';
import 'package:diplomaticquarterapp/pages/login/login.dart';
import 'package:diplomaticquarterapp/routes.dart';
import 'package:diplomaticquarterapp/uitl/navigation_service.dart';
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@ -8,7 +11,6 @@ import 'config/size_config.dart';
import 'core/viewModels/project_view_model.dart';
import 'locator.dart';
void main() async {
setupLocator();
runApp(MyApp());

@ -0,0 +1,104 @@
class IncomingCallData {
String msgID;
String notfID;
String notificationForeground;
String count;
String message;
String appointmentNo;
String title;
String projectID;
String notificationType;
String background;
String doctorname;
String clinicname;
String speciality;
String appointmentdate;
String appointmenttime;
String type;
String sessionId;
String identity;
String name;
String videoUrl;
String picture;
String isCall;
String sound;
IncomingCallData(
{this.msgID,
this.notfID,
this.notificationForeground,
this.count,
this.message,
this.appointmentNo,
this.title,
this.projectID,
this.notificationType,
this.background,
this.doctorname,
this.clinicname,
this.speciality,
this.appointmentdate,
this.appointmenttime,
this.type,
this.sessionId,
this.identity,
this.name,
this.videoUrl,
this.picture,
this.isCall,
this.sound});
IncomingCallData.fromJson(Map<String, dynamic> json) {
msgID = json['msgID'];
notfID = json['notfID'];
notificationForeground = json['notification_foreground'];
count = json['count'];
message = json['message'];
appointmentNo = json['AppointmentNo'];
title = json['title'];
projectID = json['ProjectID'];
notificationType = json['NotificationType'];
background = json['background'];
doctorname = json['doctorname'];
clinicname = json['clinicname'];
speciality = json['speciality'];
appointmentdate = json['appointmentdate'];
appointmenttime = json['appointmenttime'];
type = json['type'];
sessionId = json['session_id'];
identity = json['identity'];
name = json['name'];
videoUrl = json['videoUrl'];
picture = json['picture'];
isCall = json['is_call'];
sound = json['sound'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['msgID'] = this.msgID;
data['notfID'] = this.notfID;
data['notification_foreground'] = this.notificationForeground;
data['count'] = this.count;
data['message'] = this.message;
data['AppointmentNo'] = this.appointmentNo;
data['title'] = this.title;
data['ProjectID'] = this.projectID;
data['NotificationType'] = this.notificationType;
data['background'] = this.background;
data['doctorname'] = this.doctorname;
data['clinicname'] = this.clinicname;
data['speciality'] = this.speciality;
data['appointmentdate'] = this.appointmentdate;
data['appointmenttime'] = this.appointmenttime;
data['type'] = this.type;
data['session_id'] = this.sessionId;
data['identity'] = this.identity;
data['name'] = this.name;
data['videoUrl'] = this.videoUrl;
data['picture'] = this.picture;
data['is_call'] = this.isCall;
data['sound'] = this.sound;
return data;
}
}

@ -0,0 +1,65 @@
import 'package:diplomaticquarterapp/models/LiveCare/room_validators.dart';
import 'package:diplomaticquarterapp/models/LiveCare/twilio_enums.dart';
class RoomModel with RoomValidators {
final String name;
final bool isLoading;
final bool isSubmitted;
final String token;
final String identity;
final TwilioRoomType type;
RoomModel({
this.name,
this.isLoading = false,
this.isSubmitted = false,
this.token,
this.identity,
this.type = TwilioRoomType.groupSmall,
});
static String getTypeText(TwilioRoomType type) {
switch (type) {
case TwilioRoomType.peerToPeer:
return 'peer 2 peer';
break;
case TwilioRoomType.group:
return 'large (max 50 participants)';
break;
case TwilioRoomType.groupSmall:
return 'small (max 4 participants)';
break;
}
return '';
}
String get nameErrorText {
return isSubmitted && !nameValidator.isValid(name) ? invalidNameErrorText : null;
}
String get typeText {
return RoomModel.getTypeText(type);
}
bool get canSubmit {
return nameValidator.isValid(name);
}
RoomModel copyWith({
String name,
bool isLoading,
bool isSubmitted,
String token,
String identity,
TwilioRoomType type,
}) {
return RoomModel(
name: name ?? this.name,
token: token ?? this.token,
identity: identity ?? this.identity,
isLoading: isLoading ?? this.isLoading,
isSubmitted: isSubmitted ?? this.isSubmitted,
type: type ?? this.type,
);
}
}

@ -0,0 +1,6 @@
import 'package:diplomaticquarterapp/models/LiveCare/validators.dart';
mixin RoomValidators {
final StringValidator nameValidator = NonEmptyStringValidator();
final String invalidNameErrorText = 'Room name can\'t be empty';
}

@ -0,0 +1,17 @@
enum TwilioRoomType {
peerToPeer,
group,
groupSmall,
}
enum TwilioRoomStatus {
completed,
inProgress,
}
enum TwilioStatusCallbackMethod {
GET,
POST,
}
enum TwilioVideoCodec { VP8, H264 }

@ -0,0 +1,13 @@
abstract class StringValidator {
bool isValid(String value);
}
class NonEmptyStringValidator implements StringValidator {
@override
bool isValid(String value) {
if (value == null) {
return false;
}
return value.isNotEmpty;
}
}

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
class ClippedVideo extends StatefulWidget {
final double width;
final double height;
final Widget child;
const ClippedVideo({
Key key,
@required this.width,
@required this.height,
@required this.child,
}) : super(key: key);
@override
_ClippedVideoState createState() => _ClippedVideoState();
}
class _ClippedVideoState extends State<ClippedVideo> {
@override
Widget build(BuildContext context) {
return Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
border: Border.all(
color: Colors.white24,
),
),
child: ClipRRect(
child: widget.child,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
);
}
}

@ -0,0 +1,244 @@
import 'dart:async';
import 'package:after_layout/after_layout.dart';
import 'package:diplomaticquarterapp/pages/conference/widgets/circle_button.dart';
import 'package:flutter/material.dart';
class ConferenceButtonBar extends StatefulWidget {
final VoidCallback onVideoEnabled;
final VoidCallback onAudioEnabled;
final VoidCallback onHangup;
final VoidCallback onSwitchCamera;
final VoidCallback onPersonAdd;
final VoidCallback onPersonRemove;
final void Function(double) onHeight;
final VoidCallback onHide;
final VoidCallback onShow;
final Stream<bool> videoEnabled;
final Stream<bool> audioEnabled;
const ConferenceButtonBar({
Key key,
this.onVideoEnabled,
this.onAudioEnabled,
this.onHangup,
this.onSwitchCamera,
this.onPersonAdd,
this.onPersonRemove,
@required this.videoEnabled,
@required this.audioEnabled,
this.onHeight,
this.onHide,
this.onShow,
}) : assert(videoEnabled != null),
assert(audioEnabled != null),
super(key: key);
@override
_ConferenceButtonBarState createState() => _ConferenceButtonBarState();
}
class _ConferenceButtonBarState extends State<ConferenceButtonBar> with AfterLayoutMixin<ConferenceButtonBar> {
var _bottom = -100.0;
Timer _timer;
int _remaining;
var _videoEnabled = true;
var _audioEnabled = true;
double _hidden;
double _visible;
final _keyButtonBarHeight = GlobalKey();
final Duration timeout = const Duration(seconds: 5);
final Duration ms = const Duration(milliseconds: 1);
final Duration periodicDuration = const Duration(milliseconds: 100);
Timer startTimeout([int milliseconds]) {
final duration = milliseconds == null ? timeout : ms * milliseconds;
_remaining = duration.inMilliseconds;
return Timer.periodic(periodicDuration, (Timer timer) {
_remaining -= periodicDuration.inMilliseconds;
if (_remaining <= 0) {
timer.cancel();
_toggleBar();
}
});
}
void _pauseTimer() {
if (_timer == null) {
return;
}
_timer.cancel();
_timer = null;
}
void _resumeTimer() {
// resume the timer only when there is no timer active or when
// the bar is not already hidden.
if ((_timer != null && _timer.isActive) || _bottom == _hidden) {
return;
}
_timer = startTimeout(_remaining);
}
void _toggleBar() {
setState(() {
_bottom = _bottom == _visible ? _hidden : _visible;
if (_bottom == _visible && widget.onShow != null) {
widget.onShow();
}
if (_bottom == _hidden && widget.onHide != null) {
widget.onHide();
}
});
}
void _toggleBarOnEnd() {
if (_timer != null) {
if (_timer.isActive) {
_timer.cancel();
}
_timer = null;
}
if (_bottom == 0) {
_timer = startTimeout();
}
}
@override
void initState() {
super.initState();
_timer = startTimeout();
}
@override
void didChangeDependencies() {
_visible = MediaQuery.of(context).viewPadding.bottom;
super.didChangeDependencies();
}
@override
void afterFirstLayout(BuildContext context) {
final RenderBox renderBoxButtonBar = _keyButtonBarHeight.currentContext.findRenderObject();
final heightButtonBar = renderBoxButtonBar.size.height;
// Because the `didChangeDependencies` fires before the `afterFirstLayout`, we can use the `_visible` property here.
_hidden = -(heightButtonBar + _visible);
widget.onHeight(heightButtonBar);
_toggleBar();
}
@override
void dispose() {
super.dispose();
if (_timer != null && _timer.isActive) {
_timer.cancel();
_timer = null;
}
}
@override
Widget build(BuildContext context) {
return Positioned(
top: 0,
left: 0,
right: 0,
bottom: 0,
child: GestureDetector(
key: Key('show-hide-button-bar-gesture'),
behavior: HitTestBehavior.translucent,
onTapDown: (_) => _pauseTimer(),
onTapUp: (_) => _toggleBar(),
onTapCancel: () => _resumeTimer(),
child: Stack(
children: <Widget>[
AnimatedPositioned(
key: Key('button-bar'),
bottom: _bottom,
left: 0,
right: 0,
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
child: _buildRow(context),
onEnd: _toggleBarOnEnd,
),
],
),
),
);
}
void _onPressed(VoidCallback callback) {
if (callback != null) {
callback();
}
if (_timer != null && _timer.isActive) {
_timer.cancel();
}
_timer = startTimeout();
}
Widget _buildRow(BuildContext context) {
return Padding(
key: _keyButtonBarHeight,
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
CircleButton(
child: StreamBuilder<bool>(
stream: widget.videoEnabled,
initialData: _videoEnabled,
builder: (context, snapshot) {
_videoEnabled = snapshot.data;
return Icon(
_videoEnabled ? Icons.videocam : Icons.videocam_off,
color: Colors.white,
);
}),
key: Key('camera-button'),
onPressed: () => _onPressed(widget.onVideoEnabled),
),
CircleButton(
child: StreamBuilder<bool>(
stream: widget.audioEnabled,
initialData: _audioEnabled,
builder: (context, snapshot) {
_audioEnabled = snapshot.data;
return Icon(
_audioEnabled ? Icons.mic : Icons.mic_off,
color: Colors.white,
);
}),
key: Key('microphone-button'),
onPressed: () => _onPressed(widget.onAudioEnabled),
),
CircleButton(
radius: 35,
child: const RotationTransition(
turns: AlwaysStoppedAnimation<double>(135 / 360),
child: Icon(
Icons.phone,
color: Colors.white,
size: 40,
),
),
color: Colors.red.withAlpha(200),
key: Key('hangup-button'),
onPressed: () => _onPressed(widget.onHangup),
),
CircleButton(
child: const Icon(Icons.switch_camera, color: Colors.white),
key: Key('switch-camera-button'),
onPressed: () => _onPressed(widget.onSwitchCamera),
),
CircleButton(
child: const Icon(Icons.person_add, color: Colors.white),
key: Key('add-person-button'),
onPressed: () => _onPressed(widget.onPersonAdd),
onLongPress: () => _onPressed(widget.onPersonRemove),
),
],
),
);
}
}

@ -0,0 +1,388 @@
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 room...',
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 room...',
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(() {});
}
}

@ -0,0 +1,530 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:diplomaticquarterapp/pages/conference/participant_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:twilio_programmable_video/twilio_programmable_video.dart';
class ConferenceRoom with ChangeNotifier {
final String name;
final String token;
final String identity;
final StreamController<bool> _onAudioEnabledStreamController = StreamController<bool>.broadcast();
Stream<bool> onAudioEnabled;
final StreamController<bool> _onVideoEnabledStreamController = StreamController<bool>.broadcast();
Stream<bool> onVideoEnabled;
final StreamController<Exception> _onExceptionStreamController = StreamController<Exception>.broadcast();
Stream<Exception> onException;
final Completer<Room> _completer = Completer<Room>();
final List<ParticipantWidget> _participants = [];
final List<ParticipantBuffer> _participantBuffer = [];
final List<StreamSubscription> _streamSubscriptions = [];
final List<RemoteDataTrack> _dataTracks = [];
final List<String> _messages = [];
CameraCapturer _cameraCapturer;
Room _room;
Timer _timer;
ConferenceRoom({
@required this.name,
@required this.token,
@required this.identity,
}) {
onAudioEnabled = _onAudioEnabledStreamController.stream;
onVideoEnabled = _onVideoEnabledStreamController.stream;
onException = _onExceptionStreamController.stream;
}
List<ParticipantWidget> get participants {
return [..._participants];
}
Future<Room> connect() async {
print('ConferenceRoom.connect()');
try {
await TwilioProgrammableVideo.debug(dart: true, native: true);
await TwilioProgrammableVideo.setSpeakerphoneOn(true);
_cameraCapturer = CameraCapturer(CameraSource.FRONT_CAMERA);
var connectOptions = ConnectOptions(
token,
roomName: name,
preferredAudioCodecs: [OpusCodec()],
audioTracks: [LocalAudioTrack(true)],
dataTracks: [LocalDataTrack()],
videoTracks: [LocalVideoTrack(true, _cameraCapturer)],
enableDominantSpeaker: true,
);
_room = await TwilioProgrammableVideo.connect(connectOptions);
_streamSubscriptions.add(_room.onConnected.listen(_onConnected));
_streamSubscriptions.add(_room.onConnectFailure.listen(_onConnectFailure));
return _completer.future;
} catch (err) {
print(err);
rethrow;
}
}
Future<void> disconnect() async {
print('ConferenceRoom.disconnect()');
if (_timer != null) {
_timer.cancel();
}
await _room.disconnect();
}
@override
void dispose() {
print('ConferenceRoom.dispose()');
_disposeStreamsAndSubscriptions();
super.dispose();
}
Future<void> _disposeStreamsAndSubscriptions() async {
await _onAudioEnabledStreamController.close();
await _onVideoEnabledStreamController.close();
await _onExceptionStreamController.close();
for (var streamSubscription in _streamSubscriptions) {
await streamSubscription.cancel();
}
}
Future<void> sendMessage(String message) async {
final tracks = _room.localParticipant.localDataTracks;
final localDataTrack = tracks.isEmpty ? null : tracks[0].localDataTrack;
if (localDataTrack == null || _messages.isNotEmpty) {
print('ConferenceRoom.sendMessage => Track is not available yet, buffering message.');
_messages.add(message);
return;
}
await localDataTrack.send(message);
}
Future<void> sendBufferMessage(ByteBuffer message) async {
final tracks = _room.localParticipant.localDataTracks;
final localDataTrack = tracks.isEmpty ? null : tracks[0].localDataTrack;
if (localDataTrack == null) {
return;
}
await localDataTrack.sendBuffer(message);
}
Future<void> toggleVideoEnabled() async {
final tracks = _room.localParticipant.localVideoTracks;
final localVideoTrack = tracks.isEmpty ? null : tracks[0].localVideoTrack;
if (localVideoTrack == null) {
print('ConferenceRoom.toggleVideoEnabled() => Track is not available yet!');
return;
}
await localVideoTrack.enable(!localVideoTrack.isEnabled);
var index = _participants.indexWhere((ParticipantWidget participant) => !participant.isRemote);
if (index < 0) {
return;
}
var participant = _participants[index];
_participants.replaceRange(
index,
index + 1,
[
participant.copyWith(videoEnabled: localVideoTrack.isEnabled),
],
);
print('ConferenceRoom.toggleVideoEnabled() => ${localVideoTrack.isEnabled}');
_onVideoEnabledStreamController.add(localVideoTrack.isEnabled);
notifyListeners();
}
Future<void> toggleAudioEnabled() async {
final tracks = _room.localParticipant.localAudioTracks;
final localAudioTrack = tracks.isEmpty ? null : tracks[0].localAudioTrack;
if (localAudioTrack == null) {
print('ConferenceRoom.toggleAudioEnabled() => Track is not available yet!');
return;
}
await localAudioTrack.enable(!localAudioTrack.isEnabled);
var index = _participants.indexWhere((ParticipantWidget participant) => !participant.isRemote);
if (index < 0) {
return;
}
var participant = _participants[index];
_participants.replaceRange(
index,
index + 1,
[
participant.copyWith(audioEnabled: localAudioTrack.isEnabled),
],
);
print('ConferenceRoom.toggleAudioEnabled() => ${localAudioTrack.isEnabled}');
_onAudioEnabledStreamController.add(localAudioTrack.isEnabled);
notifyListeners();
}
Future<void> switchCamera() async {
print('ConferenceRoom.switchCamera()');
try {
await _cameraCapturer.switchCamera();
} on FormatException catch (e) {
print(
'ConferenceRoom.switchCamera() failed because of FormatException with message: ${e.message}',
);
}
}
void addDummy({Widget child}) {
print('ConferenceRoom.addDummy()');
if (_participants.length >= 18) {
throw PlatformException(
code: 'ConferenceRoom.maximumReached',
message: 'Maximum reached',
details: 'Currently the lay-out can only render a maximum of 18 participants',
);
}
_participants.insert(
0,
ParticipantWidget(
id: (_participants.length + 1).toString(),
child: child,
isRemote: true,
audioEnabled: true,
videoEnabled: true,
isDummy: true,
),
);
notifyListeners();
}
void removeDummy() {
print('ConferenceRoom.removeDummy()');
var dummy = _participants.firstWhere((participant) => participant.isDummy, orElse: () => null);
if (dummy != null) {
_participants.remove(dummy);
notifyListeners();
}
}
void _onConnected(Room room) {
print('ConferenceRoom._onConnected => state: ${room.state}');
// When connected for the first time, add remote participant listeners
_streamSubscriptions.add(_room.onParticipantConnected.listen(_onParticipantConnected));
_streamSubscriptions.add(_room.onParticipantDisconnected.listen(_onParticipantDisconnected));
_streamSubscriptions.add(_room.onDominantSpeakerChange.listen(_onDominantSpeakerChanged));
// Only add ourselves when connected for the first time too.
_participants.add(
_buildParticipant(
child: room.localParticipant.localVideoTracks[0].localVideoTrack.widget(),
id: identity,
audioEnabled: true,
videoEnabled: true,
),
);
for (final remoteParticipant in room.remoteParticipants) {
var participant = _participants.firstWhere((participant) => participant.id == remoteParticipant.sid, orElse: () => null);
if (participant == null) {
print('Adding participant that was already present in the room ${remoteParticipant.sid}, before I connected');
_addRemoteParticipantListeners(remoteParticipant);
}
}
// We have to listen for the [onDataTrackPublished] event on the [LocalParticipant] in
// order to be able to use the [send] method.
_streamSubscriptions.add(room.localParticipant.onDataTrackPublished.listen(_onLocalDataTrackPublished));
notifyListeners();
_completer.complete(room);
_timer = Timer.periodic(const Duration(minutes: 1), (_) {
// Let's see if we can send some data over the DataTrack API
sendMessage('And another minute has passed since I connected...');
// Also try the ByteBuffer way of sending data
final list = 'This data has been sent over the ByteBuffer channel of the DataTrack API'.codeUnits;
var bytes = Uint8List.fromList(list);
sendBufferMessage(bytes.buffer);
});
}
void _onLocalDataTrackPublished(LocalDataTrackPublishedEvent event) {
// Send buffered messages, if any...
while (_messages.isNotEmpty) {
var message = _messages.removeAt(0);
print('Sending buffered message: $message');
event.localDataTrackPublication.localDataTrack.send(message);
}
}
void _onConnectFailure(RoomConnectFailureEvent event) {
print('ConferenceRoom._onConnectFailure: ${event.exception}');
_completer.completeError(event.exception);
}
void _onDominantSpeakerChanged(DominantSpeakerChangedEvent event) {
print('ConferenceRoom._onDominantSpeakerChanged: ${event.remoteParticipant.identity}');
var oldDominantParticipant = _participants.firstWhere((p) => p.isDominant, orElse: () => null);
if (oldDominantParticipant != null) {
var oldDominantParticipantIndex = _participants.indexOf(oldDominantParticipant);
_participants.replaceRange(oldDominantParticipantIndex, oldDominantParticipantIndex + 1, [oldDominantParticipant.copyWith(isDominant: false)]);
}
var newDominantParticipant = _participants.firstWhere((p) => p.id == event.remoteParticipant.sid);
var newDominantParticipantIndex = _participants.indexOf(newDominantParticipant);
_participants.replaceRange(newDominantParticipantIndex, newDominantParticipantIndex + 1, [newDominantParticipant.copyWith(isDominant: true)]);
notifyListeners();
}
void _onParticipantConnected(RoomParticipantConnectedEvent event) {
print('ConferenceRoom._onParticipantConnected, ${event.remoteParticipant.sid}');
_addRemoteParticipantListeners(event.remoteParticipant);
}
void _onParticipantDisconnected(RoomParticipantDisconnectedEvent event) {
print('ConferenceRoom._onParticipantDisconnected: ${event.remoteParticipant.sid}');
_participants.removeWhere((ParticipantWidget p) => p.id == event.remoteParticipant.sid);
notifyListeners();
}
ParticipantWidget _buildParticipant({
@required Widget child,
@required String id,
@required bool audioEnabled,
@required bool videoEnabled,
RemoteParticipant remoteParticipant,
}) {
return ParticipantWidget(
id: remoteParticipant?.sid,
isRemote: remoteParticipant != null,
child: child,
audioEnabled: audioEnabled,
videoEnabled: videoEnabled,
);
}
void _addRemoteParticipantListeners(RemoteParticipant remoteParticipant) {
print('ConferenceRoom._addRemoteParticipantListeners() => Adding listeners to remoteParticipant ${remoteParticipant.sid}');
_streamSubscriptions.add(remoteParticipant.onAudioTrackDisabled.listen(_onAudioTrackDisabled));
_streamSubscriptions.add(remoteParticipant.onAudioTrackEnabled.listen(_onAudioTrackEnabled));
_streamSubscriptions.add(remoteParticipant.onAudioTrackPublished.listen(_onAudioTrackPublished));
_streamSubscriptions.add(remoteParticipant.onAudioTrackSubscribed.listen(_onAudioTrackSubscribed));
_streamSubscriptions.add(remoteParticipant.onAudioTrackSubscriptionFailed.listen(_onAudioTrackSubscriptionFailed));
_streamSubscriptions.add(remoteParticipant.onAudioTrackUnpublished.listen(_onAudioTrackUnpublished));
_streamSubscriptions.add(remoteParticipant.onAudioTrackUnsubscribed.listen(_onAudioTrackUnsubscribed));
_streamSubscriptions.add(remoteParticipant.onDataTrackPublished.listen(_onDataTrackPublished));
_streamSubscriptions.add(remoteParticipant.onDataTrackSubscribed.listen(_onDataTrackSubscribed));
_streamSubscriptions.add(remoteParticipant.onDataTrackSubscriptionFailed.listen(_onDataTrackSubscriptionFailed));
_streamSubscriptions.add(remoteParticipant.onDataTrackUnpublished.listen(_onDataTrackUnpublished));
_streamSubscriptions.add(remoteParticipant.onDataTrackUnsubscribed.listen(_onDataTrackUnsubscribed));
_streamSubscriptions.add(remoteParticipant.onVideoTrackDisabled.listen(_onVideoTrackDisabled));
_streamSubscriptions.add(remoteParticipant.onVideoTrackEnabled.listen(_onVideoTrackEnabled));
_streamSubscriptions.add(remoteParticipant.onVideoTrackPublished.listen(_onVideoTrackPublished));
_streamSubscriptions.add(remoteParticipant.onVideoTrackSubscribed.listen(_onVideoTrackSubscribed));
_streamSubscriptions.add(remoteParticipant.onVideoTrackSubscriptionFailed.listen(_onVideoTrackSubscriptionFailed));
_streamSubscriptions.add(remoteParticipant.onVideoTrackUnpublished.listen(_onVideoTrackUnpublished));
_streamSubscriptions.add(remoteParticipant.onVideoTrackUnsubscribed.listen(_onVideoTrackUnsubscribed));
}
void _onAudioTrackDisabled(RemoteAudioTrackEvent event) {
print('ConferenceRoom._onAudioTrackDisabled(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}, isEnabled: ${event.remoteAudioTrackPublication.isTrackEnabled}');
_setRemoteAudioEnabled(event);
}
void _onAudioTrackEnabled(RemoteAudioTrackEvent event) {
print('ConferenceRoom._onAudioTrackEnabled(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}, isEnabled: ${event.remoteAudioTrackPublication.isTrackEnabled}');
_setRemoteAudioEnabled(event);
}
void _onAudioTrackPublished(RemoteAudioTrackEvent event) {
print('ConferenceRoom._onAudioTrackPublished(), ${event.remoteParticipant.sid}}');
}
void _onAudioTrackSubscribed(RemoteAudioTrackSubscriptionEvent event) {
print('ConferenceRoom._onAudioTrackSubscribed(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}');
_addOrUpdateParticipant(event);
}
void _onAudioTrackSubscriptionFailed(RemoteAudioTrackSubscriptionFailedEvent event) {
print('ConferenceRoom._onAudioTrackSubscriptionFailed(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}');
_onExceptionStreamController.add(
PlatformException(
code: 'ConferenceRoom.audioTrackSubscriptionFailed',
message: 'AudioTrack Subscription Failed',
details: event.exception.toString(),
),
);
}
void _onAudioTrackUnpublished(RemoteAudioTrackEvent event) {
print('ConferenceRoom._onAudioTrackUnpublished(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}');
}
void _onAudioTrackUnsubscribed(RemoteAudioTrackSubscriptionEvent event) {
print('ConferenceRoom._onAudioTrackUnsubscribed(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrack.sid}');
}
void _onDataTrackPublished(RemoteDataTrackEvent event) {
print('ConferenceRoom._onDataTrackPublished(), ${event.remoteParticipant.sid}}');
}
void _onDataTrackSubscribed(RemoteDataTrackSubscriptionEvent event) {
print('ConferenceRoom._onDataTrackSubscribed(), ${event.remoteParticipant.sid}, ${event.remoteDataTrackPublication.trackSid}');
final dataTrack = event.remoteDataTrackPublication.remoteDataTrack;
_dataTracks.add(dataTrack);
_streamSubscriptions.add(dataTrack.onMessage.listen(_onMessage));
_streamSubscriptions.add(dataTrack.onBufferMessage.listen(_onBufferMessage));
}
void _onDataTrackSubscriptionFailed(RemoteDataTrackSubscriptionFailedEvent event) {
print('ConferenceRoom._onDataTrackSubscriptionFailed(), ${event.remoteParticipant.sid}, ${event.remoteDataTrackPublication.trackSid}');
_onExceptionStreamController.add(
PlatformException(
code: 'ConferenceRoom.dataTrackSubscriptionFailed',
message: 'DataTrack Subscription Failed',
details: event.exception.toString(),
),
);
}
void _onDataTrackUnpublished(RemoteDataTrackEvent event) {
print('ConferenceRoom._onDataTrackUnpublished(), ${event.remoteParticipant.sid}, ${event.remoteDataTrackPublication.trackSid}');
}
void _onDataTrackUnsubscribed(RemoteDataTrackSubscriptionEvent event) {
print('ConferenceRoom._onDataTrackUnsubscribed(), ${event.remoteParticipant.sid}, ${event.remoteDataTrack.sid}');
}
void _onVideoTrackDisabled(RemoteVideoTrackEvent event) {
print('ConferenceRoom._onVideoTrackDisabled(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}, isEnabled: ${event.remoteVideoTrackPublication.isTrackEnabled}');
_setRemoteVideoEnabled(event);
}
void _onVideoTrackEnabled(RemoteVideoTrackEvent event) {
print('ConferenceRoom._onVideoTrackEnabled(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}, isEnabled: ${event.remoteVideoTrackPublication.isTrackEnabled}');
_setRemoteVideoEnabled(event);
}
void _onVideoTrackPublished(RemoteVideoTrackEvent event) {
print('ConferenceRoom._onVideoTrackPublished(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}');
}
void _onVideoTrackSubscribed(RemoteVideoTrackSubscriptionEvent event) {
print('ConferenceRoom._onVideoTrackSubscribed(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrack.sid}');
_addOrUpdateParticipant(event);
}
void _onVideoTrackSubscriptionFailed(RemoteVideoTrackSubscriptionFailedEvent event) {
print('ConferenceRoom._onVideoTrackSubscriptionFailed(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}');
_onExceptionStreamController.add(
PlatformException(
code: 'ConferenceRoom.videoTrackSubscriptionFailed',
message: 'VideoTrack Subscription Failed',
details: event.exception.toString(),
),
);
}
void _onVideoTrackUnpublished(RemoteVideoTrackEvent event) {
print('ConferenceRoom._onVideoTrackUnpublished(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}');
}
void _onVideoTrackUnsubscribed(RemoteVideoTrackSubscriptionEvent event) {
print('ConferenceRoom._onVideoTrackUnsubscribed(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrack.sid}');
}
void _onMessage(RemoteDataTrackStringMessageEvent event) {
print('onMessage => ${event.remoteDataTrack.sid}, ${event.message}');
}
void _onBufferMessage(RemoteDataTrackBufferMessageEvent event) {
print('onBufferMessage => ${event.remoteDataTrack.sid}, ${String.fromCharCodes(event.message.asUint8List())}');
}
void _setRemoteAudioEnabled(RemoteAudioTrackEvent event) {
if (event.remoteAudioTrackPublication == null) {
return;
}
var index = _participants.indexWhere((ParticipantWidget participant) => participant.id == event.remoteParticipant.sid);
if (index < 0) {
return;
}
var participant = _participants[index];
_participants.replaceRange(
index,
index + 1,
[
participant.copyWith(audioEnabled: event.remoteAudioTrackPublication.isTrackEnabled),
],
);
notifyListeners();
}
void _setRemoteVideoEnabled(RemoteVideoTrackEvent event) {
if (event.remoteVideoTrackPublication == null) {
return;
}
var index = _participants.indexWhere((ParticipantWidget participant) => participant.id == event.remoteParticipant.sid);
if (index < 0) {
return;
}
var participant = _participants[index];
_participants.replaceRange(
index,
index + 1,
[
participant.copyWith(videoEnabled: event.remoteVideoTrackPublication.isTrackEnabled),
],
);
notifyListeners();
}
void _addOrUpdateParticipant(RemoteParticipantEvent event) {
print('ConferenceRoom._addOrUpdateParticipant(), ${event.remoteParticipant.sid}');
final participant = _participants.firstWhere(
(ParticipantWidget participant) => participant.id == event.remoteParticipant.sid,
orElse: () => null,
);
if (participant != null) {
print('Participant found: ${participant.id}, updating A/V enabled values');
_setRemoteVideoEnabled(event);
_setRemoteAudioEnabled(event);
} else {
final bufferedParticipant = _participantBuffer.firstWhere(
(ParticipantBuffer participant) => participant.id == event.remoteParticipant.sid,
orElse: () => null,
);
if (bufferedParticipant != null) {
_participantBuffer.remove(bufferedParticipant);
} else if (event is RemoteAudioTrackEvent) {
print('Audio subscription came first, waiting for the video subscription...');
_participantBuffer.add(
ParticipantBuffer(
id: event.remoteParticipant.sid,
audioEnabled: event.remoteAudioTrackPublication?.remoteAudioTrack?.isEnabled ?? true,
),
);
return;
}
if (event is RemoteVideoTrackSubscriptionEvent) {
print('New participant, adding: ${event.remoteParticipant.sid}');
_participants.insert(
0,
_buildParticipant(
child: event.remoteVideoTrack.widget(),
id: event.remoteParticipant.sid,
remoteParticipant: event.remoteParticipant,
audioEnabled: bufferedParticipant?.audioEnabled ?? true,
videoEnabled: event.remoteVideoTrackPublication?.remoteVideoTrack?.isEnabled ?? true,
),
);
}
notifyListeners();
}
}
}

@ -0,0 +1,173 @@
import 'dart:async';
import 'dart:io';
import 'package:diplomaticquarterapp/pages/conference/clipped_video.dart';
import 'package:flutter/material.dart';
class DraggablePublisher extends StatefulWidget {
final Size availableScreenSize;
final Widget child;
final double scaleFactor;
final Stream<bool> onButtonBarVisible;
final Stream<double> onButtonBarHeight;
const DraggablePublisher({
Key key,
@required this.availableScreenSize,
this.child,
@required this.onButtonBarVisible,
@required this.onButtonBarHeight,
/// The portion of the screen the DraggableWidget should use.
this.scaleFactor = .25,
}) : assert(scaleFactor != null && scaleFactor > 0 && scaleFactor <= .4),
assert(availableScreenSize != null),
assert(onButtonBarVisible != null),
assert(onButtonBarHeight != null),
super(key: key);
@override
_DraggablePublisherState createState() => _DraggablePublisherState();
}
class _DraggablePublisherState extends State<DraggablePublisher> {
bool _isButtonBarVisible = true;
double _buttonBarHeight = 0;
double _width;
double _height;
double _top;
double _left;
double _viewPaddingTop;
double _viewPaddingBottom;
final double _padding = 8.0;
final Duration _duration300ms = const Duration(milliseconds: 300);
final Duration _duration0ms = const Duration(milliseconds: 0);
Duration _duration;
StreamSubscription _streamSubscription;
StreamSubscription _streamHeightSubscription;
@override
void initState() {
super.initState();
_duration = _duration300ms;
_width = widget.availableScreenSize.width * widget.scaleFactor;
_height = _width * (widget.availableScreenSize.height / widget.availableScreenSize.width);
_top = widget.availableScreenSize.height - (_buttonBarHeight + _padding) - _height;
_left = widget.availableScreenSize.width - _padding - _width;
_streamSubscription = widget.onButtonBarVisible.listen(_buttonBarVisible);
_streamHeightSubscription = widget.onButtonBarHeight.listen(_getButtonBarHeight);
}
@override
void didChangeDependencies() {
final mediaQuery = MediaQuery.of(context);
_viewPaddingTop = mediaQuery.viewPadding.top;
_viewPaddingBottom = mediaQuery.viewPadding.bottom;
super.didChangeDependencies();
}
@override
void dispose() {
_streamSubscription.cancel();
_streamHeightSubscription.cancel();
super.dispose();
}
void _getButtonBarHeight(double height) {
setState(() {
_buttonBarHeight = height;
_positionWidget();
});
}
void _buttonBarVisible(bool visible) {
if (!mounted) {
return;
}
setState(() {
_isButtonBarVisible = visible;
if (_duration == _duration300ms) {
// only position the widget when we are not currently dragging it around
_positionWidget();
}
});
}
@override
Widget build(BuildContext context) {
return AnimatedPositioned(
top: _top,
left: _left,
width: _width,
height: _height,
duration: _duration,
child: Listener(
onPointerDown: (_) => _duration = _duration0ms,
onPointerMove: (PointerMoveEvent event) {
setState(() {
_left = (_left + event.delta.dx).roundToDouble();
_top = (_top + event.delta.dy).roundToDouble();
});
},
onPointerUp: (_) => _positionWidget(),
onPointerCancel: (_) => _positionWidget(),
child: ClippedVideo(
height: _height,
width: _width,
child: widget.child,
),
),
);
}
double _getCurrentStatusBarHeight() {
if (_isButtonBarVisible) {
return _viewPaddingTop;
}
final _defaultViewPaddingTop = Platform.isIOS ? 20.0 : Platform.isAndroid ? 24.0 : 0.0;
if (_viewPaddingTop > _defaultViewPaddingTop) {
// There must be a hardware notch in the display.
return _viewPaddingTop;
}
return 0.0;
}
double _getCurrentButtonBarHeight() {
if (_isButtonBarVisible) {
return _buttonBarHeight + _viewPaddingBottom;
}
return _viewPaddingBottom;
}
void _positionWidget() {
// Determine the center of the object being dragged so we can decide
// in which corner the object should be placed.
var dx = (_width / 2) + _left;
dx = dx < 0 ? 0 : dx >= widget.availableScreenSize.width ? widget.availableScreenSize.width - 1 : dx;
var dy = (_height / 2) + _top;
dy = dy < 0 ? 0 : dy >= widget.availableScreenSize.height ? widget.availableScreenSize.height - 1 : dy;
final draggableCenter = Offset(dx, dy);
setState(() {
_duration = _duration300ms;
if (Rect.fromLTRB(0, 0, widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2).contains(draggableCenter)) {
// Top-left
_top = _getCurrentStatusBarHeight() + _padding;
_left = _padding;
} else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, 0, widget.availableScreenSize.width, widget.availableScreenSize.height / 2).contains(draggableCenter)) {
// Top-right
_top = _getCurrentStatusBarHeight() + _padding;
_left = widget.availableScreenSize.width - _padding - _width;
} else if (Rect.fromLTRB(0, widget.availableScreenSize.height / 2, widget.availableScreenSize.width / 2, widget.availableScreenSize.height).contains(draggableCenter)) {
// Bottom-left
_top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height;
_left = _padding;
} else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2, widget.availableScreenSize.width, widget.availableScreenSize.height).contains(draggableCenter)) {
// Bottom-right
_top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height;
_left = widget.availableScreenSize.width - _padding - _width;
}
});
}
}

@ -0,0 +1,197 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class ParticipantBuffer {
final bool audioEnabled;
final String id;
ParticipantBuffer({
@required this.audioEnabled,
@required this.id,
}) : assert(audioEnabled != null),
assert(id != null);
}
class ParticipantWidget extends StatelessWidget {
final Widget child;
final String id;
final bool audioEnabled;
final bool videoEnabled;
final bool isRemote;
final bool isDummy;
final bool isDominant;
const ParticipantWidget({
Key key,
@required this.child,
@required this.audioEnabled,
@required this.videoEnabled,
@required this.id,
@required this.isRemote,
this.isDominant = false,
this.isDummy = false,
}) : assert(child != null),
assert(audioEnabled != null),
assert(videoEnabled != null),
assert(isRemote != null),
assert(isDominant != null),
assert(isDummy != null),
super(key: key);
ParticipantWidget copyWith({
Widget child,
bool audioEnabled,
bool videoEnabled,
bool isDominant,
}) {
return ParticipantWidget(
id: id,
child: child ?? this.child,
audioEnabled: audioEnabled ?? this.audioEnabled,
videoEnabled: videoEnabled ?? this.videoEnabled,
isDominant: isDominant ?? this.isDominant,
isRemote: isRemote,
);
}
@override
Widget build(BuildContext context) {
final children = <Widget>[];
final icons = <Widget>[];
if (!videoEnabled) {
icons.add(_buildVideoEnabledIcon());
children.add(
ClipRect(
// Need to clip this BackdropFilter, otherwise it will blur the entire screen
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(color: Colors.black.withOpacity(.1)),
child: child,
),
),
),
);
} else {
children.add(child);
}
children.add(Padding(
padding: const EdgeInsets.all(8.0),
child: AnimatedOpacity(
duration: Duration(milliseconds: 500),
opacity: isDominant ? 1 : 0,
child: Icon(
Icons.volume_up,
color: Colors.white,
),
),
));
if (!audioEnabled) {
icons.add(_buildAudioEnabledIcon());
}
if (icons.isNotEmpty) {
if (isRemote) {
final rows = <Widget>[];
rows.add(_buildRow(icons));
if (!audioEnabled && !videoEnabled) {
rows.add(_buildRow(_fitText('The camera and microphone are off', Colors.white24)));
} else if (!audioEnabled) {
rows.add(_buildRow(_fitText('The microphone is off', Colors.black26)));
} else if (!videoEnabled) {
rows.add(_buildRow(_fitText('The camera is off', Colors.white24)));
}
children.add(
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: rows,
),
);
} else {
children.add(Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: icons,
));
}
}
return Stack(
children: children,
);
}
List<Widget> _fitText(String text, Color color) {
return [
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: Text(text, maxLines: 1, style: _buildTextStyle(color)),
),
),
),
];
}
TextStyle _buildTextStyle(Color color) {
return TextStyle(
color: color,
shadows: <Shadow>[
Shadow(
blurRadius: 1.0,
color: Color.fromARGB(255, 0, 0, 0),
),
Shadow(
blurRadius: 1.0,
color: Color.fromARGB(24, 255, 255, 255),
),
],
fontSize: 15,
);
}
Widget _buildRow(List<Widget> children) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
);
}
Widget _buildAudioEnabledIcon() {
return Padding(
padding: const EdgeInsets.all(8),
child: CircleAvatar(
maxRadius: 15,
child: FittedBox(
child: Icon(
Icons.mic_off,
color: Colors.black,
key: Key('microphone-off-icon'),
),
),
backgroundColor: Colors.white24,
),
);
}
Widget _buildVideoEnabledIcon() {
return Padding(
padding: const EdgeInsets.all(8),
child: CircleAvatar(
maxRadius: 15,
child: FittedBox(
child: Icon(
Icons.videocam_off,
color: Colors.black,
key: Key('videocam-off-icon'),
),
),
backgroundColor: Colors.white24,
),
);
}
}

@ -0,0 +1,106 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
class ButtonToProgress extends StatefulWidget {
final double height;
final double progressHeight;
final String loadingText;
final Duration duration;
final TextStyle loadingTextStyle;
final VoidCallback onPressed;
final Stream<bool> onLoading;
final Widget child;
const ButtonToProgress({
Key key,
this.height = 40.0,
this.progressHeight = 5.0,
this.loadingText,
this.duration = const Duration(milliseconds: 300),
this.loadingTextStyle,
this.onPressed,
this.onLoading,
@required this.child,
}) : assert(child != null),
assert(height != null && height > 0),
assert(progressHeight != null && progressHeight > 0 && progressHeight <= height),
super(key: key);
@override
_ButtonToProgressState createState() => _ButtonToProgressState();
}
class _ButtonToProgressState extends State<ButtonToProgress> {
double _height;
double _opacity = 0;
bool _isLoading = false;
StreamSubscription<bool> _subscription;
@override
void initState() {
super.initState();
_height = widget.height;
if (widget.onLoading != null) {
_subscription = widget.onLoading.listen((bool isLoading) {
setState(() {
_isLoading = isLoading;
_height = isLoading ? widget.progressHeight : widget.height;
_opacity = isLoading ? 1 : 0;
});
});
}
}
@override
void dispose() {
if (_subscription != null) {
_subscription.cancel();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
height: widget.height,
child: Stack(
children: [
if (widget.loadingText == null)
Container()
else
Padding(
padding: EdgeInsets.only(bottom: widget.progressHeight),
child: AnimatedOpacity(
child: Center(
child: FittedBox(
child: Text(
widget.loadingText,
style: widget.loadingTextStyle,
),
),
),
opacity: _opacity,
duration: Duration(milliseconds: widget.duration.inMilliseconds + 200),
curve: Curves.easeInCubic,
),
),
AnimatedPadding(
duration: widget.duration,
padding: EdgeInsets.only(
top: math.max(widget.height - _height, 0),
),
child: AnimatedContainer(
duration: widget.duration,
height: _height,
width: double.infinity,
child: _isLoading ? const LinearProgressIndicator() : widget.child,
),
),
],
),
);
}
}

@ -0,0 +1,103 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:native_device_orientation/native_device_orientation.dart';
class CircleButton extends StatefulWidget {
final VoidCallback onLongPress;
final VoidCallback onPressed;
final GestureTapDownCallback onTapDown;
final VoidCallback onTapCancel;
final Widget child;
final Color color;
final double radius;
const CircleButton({
Key key,
this.onLongPress,
this.onPressed,
this.child,
this.color,
this.radius = 25.0,
this.onTapCancel,
this.onTapDown,
}) : assert(radius != null),
super(key: key);
@override
_CircleButtonState createState() => _CircleButtonState();
}
class _CircleButtonState extends State<CircleButton> {
double _rotationAngle = 0.0;
final Stream<NativeDeviceOrientation> _orientationStream = NativeDeviceOrientationCommunicator().onOrientationChanged(useSensor: true);
StreamSubscription<NativeDeviceOrientation> _orientationSubscription;
void _handleOrientationChange(NativeDeviceOrientation orientation) {
var targetAngle = 0.0;
switch (orientation) {
case NativeDeviceOrientation.unknown:
case NativeDeviceOrientation.portraitUp:
targetAngle = 0.0;
break;
case NativeDeviceOrientation.portraitDown:
targetAngle = 180.0;
break;
case NativeDeviceOrientation.landscapeLeft:
targetAngle = 90.0;
break;
case NativeDeviceOrientation.landscapeRight:
targetAngle = 270.0;
break;
}
setState(() {
_rotationAngle = targetAngle;
});
}
@override
void initState() {
super.initState();
_orientationSubscription = _orientationStream.listen(
_handleOrientationChange,
onError: (dynamic err) => print(err),
);
}
@override
void dispose() {
super.dispose();
_orientationSubscription.cancel();
}
@override
Widget build(BuildContext context) {
final size = 2 * widget.radius;
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: (widget.color ?? Theme.of(context).primaryColor).withAlpha(200),
borderRadius: BorderRadius.all(
Radius.circular(widget.radius),
),
),
child: GestureDetector(
onLongPress: widget.onLongPress,
onTapDown: widget.onTapDown,
onTapCancel: widget.onTapCancel,
child: RawMaterialButton(
onPressed: widget.onPressed,
child: RotationTransition(
child: widget.child,
turns: AlwaysStoppedAnimation<double>(_rotationAngle / 360),
),
elevation: 0,
shape: const CircleBorder(),
),
),
);
}
}

@ -0,0 +1,144 @@
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
enum NoiseBoxDensity {
high,
medium,
low,
xHigh,
xLow,
}
class NoiseBox extends StatefulWidget {
final NoiseBoxDensity density;
final Color backgroundColor;
final Widget child;
const NoiseBox({
Key key,
this.backgroundColor,
this.child,
this.density = NoiseBoxDensity.medium,
}) : assert(density != null),
super(key: key);
@override
_NoiseBoxState createState() => _NoiseBoxState();
}
class _NoiseBoxState extends State<NoiseBox> with SingleTickerProviderStateMixin {
AnimationController _animationController;
int _density;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 60),
);
_animationController.repeat();
switch (widget.density) {
case NoiseBoxDensity.high:
_density = 5;
break;
case NoiseBoxDensity.medium:
_density = 7;
break;
case NoiseBoxDensity.low:
_density = 10;
break;
case NoiseBoxDensity.xHigh:
_density = 3;
break;
case NoiseBoxDensity.xLow:
_density = 12;
break;
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => Container(
color: widget.backgroundColor,
width: constraints.biggest.width,
height: constraints.biggest.height,
child: AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, Widget w) {
final children = <Widget>[
CustomPaint(
painter: NoisePainter(
width: constraints.biggest.width,
height: constraints.biggest.height,
density: _density,
),
),
];
if (widget.child != null) {
children.add(widget.child);
}
return Stack(
children: children,
);
},
),
),
);
}
}
class NoisePainter extends CustomPainter {
final double width;
final double height;
final int density;
NoisePainter({
@required this.width,
@required this.height,
@required this.density,
}) : assert(width != null),
assert(height != null),
assert(density != null && density >= 3 && density < math.min(width, height));
List<Color> colors = <Color>[
Colors.black,
Colors.grey,
Colors.blueGrey,
Colors.red,
Colors.green,
Colors.blue,
Colors.white,
];
@override
void paint(Canvas canvas, Size size) {
final random = math.Random();
for (var w = 0; w < width; w += density) {
for (var h = 0; h < height; h += density) {
final offset = Offset(
random.nextDouble() * width,
random.nextDouble() * height,
);
final paint = Paint();
paint.color = colors[random.nextInt(colors.length)];
paint.strokeWidth = random.nextDouble() * 2;
canvas.drawPoints(PointMode.points, <Offset>[offset], paint);
}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

@ -0,0 +1,98 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import './platform_widget.dart';
class PlatformAlertDialog extends PlatformWidget {
PlatformAlertDialog({@required this.title, @required this.content, @required this.defaultActionText, this.cancelActionText})
: assert(title != null),
assert(content != null),
assert(defaultActionText != null);
final String title;
final String content;
final String defaultActionText;
final String cancelActionText;
Future<bool> show(BuildContext context) async {
return Platform.isIOS
? await showCupertinoDialog<bool>(
context: context,
builder: (BuildContext context) => this,
)
: await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => this,
);
}
@override
Widget buildCupertinoWidget(BuildContext context) {
return CupertinoAlertDialog(
title: Text(title),
content: Text(content),
actions: _buildActions(context),
);
}
@override
Widget buildMaterialWidget(BuildContext context) {
return AlertDialog(
title: Text(title),
content: Text(content),
actions: _buildActions(context),
);
}
List<Widget> _buildActions(BuildContext context) {
final actions = <Widget>[];
if (cancelActionText != null) {
actions.add(
PlatformAlertDialogAction(
child: Text(cancelActionText),
onPressed: () {
Navigator.of(context).pop(false);
},
),
);
}
actions.add(
PlatformAlertDialogAction(
child: Text(defaultActionText),
onPressed: () {
Navigator.of(context).pop(true);
},
),
);
return actions;
}
}
class PlatformAlertDialogAction extends PlatformWidget {
PlatformAlertDialogAction({
this.child,
this.onPressed,
});
final Widget child;
final VoidCallback onPressed;
@override
Widget buildCupertinoWidget(BuildContext context) {
return CupertinoDialogAction(
child: child,
onPressed: onPressed,
);
}
@override
Widget buildMaterialWidget(BuildContext context) {
return FlatButton(
child: child,
onPressed: onPressed,
);
}
}

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './platform_alert_dialog.dart';
class PlatformExceptionAlertDialog extends PlatformAlertDialog {
PlatformExceptionAlertDialog({
String title = 'An error occurred',
@required Exception exception,
}) : super(
title: title,
content: exception is PlatformException ? _message(exception) : exception.toString(),
defaultActionText: 'OK',
);
static String _message(PlatformException exception) {
return _errors[exception.code] ?? (exception.details != null ? (exception.details['message'] ?? exception.message) : exception.message);
}
static final Map<String, String> _errors = <String, String>{
'ERROR_CODE': 'Error description...',
};
}

@ -0,0 +1,16 @@
import 'dart:io';
import 'package:flutter/material.dart';
abstract class PlatformWidget extends StatelessWidget {
Widget buildCupertinoWidget(BuildContext context);
Widget buildMaterialWidget(BuildContext context);
@override
Widget build(BuildContext context) {
if (Platform.isIOS) {
return buildCupertinoWidget(context);
}
return buildMaterialWidget(context);
}
}

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
typedef ResponsiveBuilder = Widget Function(
BuildContext context,
Size size,
);
class ResponsiveSafeArea extends StatelessWidget {
const ResponsiveSafeArea({
@required ResponsiveBuilder builder,
Key key,
}) : responsiveBuilder = builder,
assert(builder != null),
super(key: key);
final ResponsiveBuilder responsiveBuilder;
@override
Widget build(BuildContext context) {
return SafeArea(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return responsiveBuilder(
context,
constraints.biggest,
);
},
),
);
}
}

@ -1,10 +1,13 @@
import 'dart:io';
import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/config/shared_pref_kay.dart';
import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart';
import 'package:diplomaticquarterapp/models/Authentication/select_device_imei_res.dart';
import 'package:diplomaticquarterapp/models/LiveCare/IncomingCallData.dart';
import 'package:diplomaticquarterapp/pages/BookAppointment/BookingOptions.dart';
import 'package:diplomaticquarterapp/pages/ToDoList/ToDo.dart';
import 'package:diplomaticquarterapp/pages/livecare/incoming_call.dart';
import 'package:diplomaticquarterapp/pages/medical/medical_profile_page.dart';
import 'package:diplomaticquarterapp/pages/medical/my_admissions_page.dart';
import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart';
@ -18,34 +21,23 @@ import 'package:permission_handler/permission_handler.dart';
import 'home_page.dart';
Future<dynamic> myBackgroundMessageHandler(
Map<String, dynamic> message) async {
if (message.containsKey('data')) {
// Handle data message
final dynamic data = message['data'];
print(data);
}
if (message.containsKey('notification')) {
// Handle notification message
final dynamic notification = message['notification'];
print(notification);
}
class LandingPage extends StatefulWidget {
static bool isOpenCallPage = false;
// Or do other work.
}
static IncomingCallData incomingCallData = new IncomingCallData();
class LandingPage extends StatefulWidget {
@override
_LandingPageState createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage> {
class _LandingPageState extends State<LandingPage> with WidgetsBindingObserver {
int currentTab = 0;
PageController pageController;
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
final authService = new AuthProvider();
bool isPageNavigated = false;
_changeCurrentTab(int tab) {
setState(() {
currentTab = tab;
@ -53,34 +45,105 @@ class _LandingPageState extends State<LandingPage> {
});
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
var route = ModalRoute.of(context);
if (route != null) {
print(route.settings.name);
}
setState(() {
print("didChangeAppLifecycleState");
print('state = $state');
AppGlobal.context = context;
if (state == AppLifecycleState.resumed) {
print(LandingPage.isOpenCallPage);
if (LandingPage.isOpenCallPage) {
if (!isPageNavigated) {
isPageNavigated = true;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IncomingCall(
incomingCallData: LandingPage.incomingCallData)))
.then((value) {
isPageNavigated = false;
});
}
}
}
if (state == AppLifecycleState.paused) {
isPageNavigated = false;
}
if (state == AppLifecycleState.inactive) {
isPageNavigated = false;
}
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
setState(() {
AppGlobal.context = context;
});
pageController = PageController(keepPage: true);
_firebaseMessaging.setAutoInitEnabled(true);
_firebaseMessaging.getToken().then((String token) {
print("Token: " + token);
sharedPref.setString(PUSH_TOKEN, token);
if (token != null) {
checkUserStatus(token);
}
requestPermissions();
//assert(token != null);
});
//_firebase Background message handler
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
// _showItemDialog(message);
if (message['data'].containsKey("is_call")) {
var route = ModalRoute.of(context);
if (route != null) {
print(route.settings.name);
}
Map<String, dynamic> myMap =
new Map<String, dynamic>.from(message['data']);
print(myMap);
LandingPage.isOpenCallPage = true;
LandingPage.incomingCallData = IncomingCallData.fromJson(myMap);
if (!isPageNavigated) {
isPageNavigated = true;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IncomingCall(
incomingCallData: LandingPage.incomingCallData)));
}
} else {
print("Is Call Not Found");
}
},
onBackgroundMessage: Platform.isIOS ? null : myBackgroundMessageHandler,
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
// _navigateToItemDetail(message);
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
// _navigateToItemDetail(message);
},
);
}
@ -164,6 +227,25 @@ class _LandingPageState extends State<LandingPage> {
}
}
static Future<dynamic> myBackgroundMessageHandler(
Map<String, dynamic> message) async {
Map<String, dynamic> myMap = new Map<String, dynamic>.from(message['data']);
print(myMap);
print("myBackgroundMessageHandler Out");
if (message.containsKey('data')) {
print("myBackgroundMessageHandler Inside");
LandingPage.incomingCallData = IncomingCallData.fromJson(myMap);
print(LandingPage.incomingCallData.doctorname);
LandingPage.isOpenCallPage = true;
}
if (message.containsKey('notification')) {
final dynamic notification = message['notification'];
print(notification);
}
}
void setUserValues(value) async {
sharedPref.setObject(IMEI_USER_DATA, value);
}

@ -0,0 +1,255 @@
import 'package:diplomaticquarterapp/models/LiveCare/IncomingCallData.dart';
import 'package:diplomaticquarterapp/models/LiveCare/room_model.dart';
import 'package:diplomaticquarterapp/pages/conference/conference_page.dart';
import 'package:diplomaticquarterapp/pages/conference/widgets/platform_exception_alert_dialog.dart';
import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
class IncomingCall extends StatefulWidget {
IncomingCallData incomingCallData;
IncomingCall({@required this.incomingCallData});
@override
_IncomingCallState createState() => _IncomingCallState();
}
class _IncomingCallState extends State<IncomingCall>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
final player = AudioPlayer();
@override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
WidgetsBinding.instance.addPostFrameCallback((_) => _runAnimation());
print(widget.incomingCallData.doctorname);
print(widget.incomingCallData.clinicname);
print(widget.incomingCallData.speciality);
super.initState();
}
@override
void dispose() {
_animationController.dispose();
player.stop();
disposeAudioResources();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
isShowAppBar: false,
body: SafeArea(
child: Container(
decoration: BoxDecoration(color: Colors.grey[700]),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 30.0),
alignment: Alignment.center,
child: Text("Incoming Video Call",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 26.0,
color: Colors.white,
letterSpacing: 1.0)),
),
Container(
alignment: Alignment.center,
margin: EdgeInsets.fromLTRB(50.0, 30.0, 50.0, 20.0),
child: Image.asset(
'assets/images/new-design/hmg_full_logo_hd_white.png'),
),
Container(
margin: EdgeInsets.fromLTRB(30.0, 10.0, 30.0, 0.0),
child: Divider(
color: Colors.white,
thickness: 1.0,
),
),
Container(
margin: EdgeInsets.only(top: 20.0),
alignment: Alignment.center,
child: Text("Dr Eyad Ismail Abu Jayab",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
letterSpacing: 0.8,
color: Colors.white)),
),
Container(
margin: EdgeInsets.only(top: 10.0),
alignment: Alignment.center,
child: Text("ENT Clinic",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
letterSpacing: 0.8,
color: Colors.white)),
),
Container(
margin: EdgeInsets.only(top: 10.0),
alignment: Alignment.center,
child: Text("Speciality",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
letterSpacing: 0.8,
color: Colors.white)),
),
Container(
decoration: BoxDecoration(
color: Colors.grey[900].withOpacity(0.8),
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
padding: EdgeInsets.all(20.0),
margin: EdgeInsets.only(top: 20.0),
child: Column(
children: <Widget>[
Text("Appointment Information",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
letterSpacing: 1.0,
color: Colors.white)),
Container(
margin: EdgeInsets.only(top: 20.0),
child: Text("Sun, 15th Dec, 2019, 09:00",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
letterSpacing: 1.0,
color: Colors.white)),
),
Container(
margin: EdgeInsets.only(top: 20.0),
child: Text("ENT Clinic",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
letterSpacing: 1.0,
color: Colors.white)),
),
],
),
),
Container(
margin: EdgeInsets.only(top: 100.0),
alignment: Alignment.center,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RotationTransition(
turns: Tween(begin: 0.0, end: -.1)
.chain(CurveTween(curve: Curves.elasticIn))
.animate(_animationController),
child: Container(
child: RawMaterialButton(
onPressed: () {
_submit();
},
elevation: 2.0,
fillColor: Colors.green,
child: Icon(
Icons.call,
color: Colors.white,
size: 35.0,
),
padding: EdgeInsets.all(15.0),
shape: CircleBorder(),
),
)),
Container(
child: RawMaterialButton(
onPressed: () {
backToHome();
},
elevation: 2.0,
fillColor: Colors.red,
child: Icon(
Icons.call_end,
color: Colors.white,
size: 35.0,
),
padding: EdgeInsets.all(15.0),
shape: CircleBorder(),
),
),
],
),
),
],
)),
),
);
}
void _runAnimation() async {
setAudioFile();
for (int i = 0; i < 100; i++) {
await _animationController.forward();
await _animationController.reverse();
}
}
Future<void> _submit() async {
backToHome();
try {
final roomModel = RoomModel(
name: widget.incomingCallData.name,
token: widget.incomingCallData.sessionId,
identity: widget.incomingCallData.identity);
await Navigator.of(context).push(
MaterialPageRoute<ConferencePage>(
fullscreenDialog: true,
builder: (BuildContext context) =>
ConferencePage(roomModel: roomModel),
),
);
} catch (err) {
print(err);
await PlatformExceptionAlertDialog(
exception: err,
).show(context);
}
}
void backToHome() {
player.stop();
disposeAudioResources();
Navigator.of(context).pop();
}
disposeAudioResources() async {
await player.dispose();
}
void setAudioFile() async {
player.stop();
await player.setVolume(1.0); // full volume
try {
await player.setAsset('assets/sounds/ring_60Sec.mp3').then((value) {
player.setLoopMode(LoopMode.one); // loop ring sound
player.play();
}).catchError((err) {
print("Error: $err");
});
} catch (e) {
print("Error: $e");
}
}
}

@ -1,5 +1,6 @@
import 'package:diplomaticquarterapp/pages/family/my-family.dart';
import 'package:diplomaticquarterapp/pages/landing/landing_page.dart';
import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart';
import 'package:diplomaticquarterapp/pages/login/confirm-login.dart';
import 'package:diplomaticquarterapp/pages/login/forgot-password.dart';
import 'package:diplomaticquarterapp/pages/login/register-info.dart';
@ -21,6 +22,7 @@ const String REGISTER = 'register';
const String CONFIRM_LOGIN = 'confrim-login';
const String REGISTER_INFO = 'register-info';
const String MY_FAMILIY = 'my-family';
const String LIVE_CARE = 'live-care';
var routes = {
// ROOT: (_) => RootPage(),
HOME: (_) => LandingPage(),
@ -31,5 +33,6 @@ var routes = {
REGISTER: (_) => Register(),
CONFIRM_LOGIN: (_) => ConfirmLogin(),
REGISTER_INFO: (_) => RegisterInfo(),
MY_FAMILIY: (_) => MyFamily()
MY_FAMILIY: (_) => MyFamily(),
LIVE_CARE: (_) => LiveCareHome()
};

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState.pushNamed(routeName);
}
}

@ -38,7 +38,7 @@ dependencies:
url_launcher: ^5.5.0
shared_preferences: ^0.5.8
flutter_flexible_toast: ^0.1.4
firebase_messaging: ^6.0.16
firebase_messaging: 6.0.12
# Progress bar
progress_hud_v2: ^2.0.0
@ -92,10 +92,19 @@ dependencies:
smart_progress_bar: ^0.1.6
#Just Audio to play ringing for incoming video call
just_audio: ^0.3.4
#hijri
hijri: ^2.0.3
#Dependencies for video call implementation
native_device_orientation: ^0.3.0
enum_to_string: ^1.0.9
# recase: ^3.0.0
wakelock: ^0.1.4
after_layout: ^1.0.7
twilio_programmable_video: ^0.5.0+3
dev_dependencies:
flutter_test:
@ -114,6 +123,7 @@ flutter:
- assets/images/new-design/
- assets/images/login/
- assets/json/
- assets/sounds/
fonts:

Loading…
Cancel
Save