diff --git a/lib/main.dart b/lib/main.dart index 92478404..abe84cdc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:doctor_app_flutter/core/viewModel/project_view_model.dart'; import 'package:doctor_app_flutter/locator.dart'; import 'package:doctor_app_flutter/routes.dart'; import 'package:doctor_app_flutter/utils/translations_delegate_base_utils.dart'; +import 'package:doctor_app_flutter/voipcall/provider/chat_call_provider.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -43,6 +44,9 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider( create: (context) => LiveCareViewModel(), ), + ChangeNotifierProvider( + create: (context) => ChatCallProvider(), + ), StreamProvider.value( value: RobotProvider().intStream(), initialData: RobotProvider().setValue({}), diff --git a/lib/screens/auth/login_screen.dart b/lib/screens/auth/login_screen.dart index 42628f04..13f0197c 100644 --- a/lib/screens/auth/login_screen.dart +++ b/lib/screens/auth/login_screen.dart @@ -6,26 +6,12 @@ import 'package:doctor_app_flutter/core/viewModel/authentication_view_model.dart import 'package:doctor_app_flutter/core/viewModel/project_view_model.dart'; import 'package:doctor_app_flutter/utils/translations_delegate_base_utils.dart'; import 'package:doctor_app_flutter/utils/utils.dart'; +import 'package:doctor_app_flutter/voipcall/provider/chat_call_provider.dart'; import 'package:doctor_app_flutter/widgets/shared/buttons/app_buttons_widget.dart'; import 'package:doctor_app_flutter/widgets/shared/loader/gif_loader_dialog_utils.dart'; import 'package:doctor_app_flutter/widgets/shared/text_fields/app-textfield-custom.dart'; import 'package:flutter/material.dart'; -import 'package:hexcolor/hexcolor.dart'; -import 'package:provider/provider.dart'; - -import '../../widgets/shared/app_scaffold_widget.dart'; - -class LoginScreen extends StatefulWidget { - @override - _LoginScreenState createState() => _LoginScreenState(); -} - -class _LoginScreenState extends State { - String? platformImei; - bool allowCallApi = true; - - //TODO change AppTextFormField to AppTextFormFieldCustom - final loginFormKey = GlobalKey(); +import 'package:hexcolor/hexcolo= GlobalKey(); var projectIdController = TextEditingController(); var userIdController = TextEditingController(); var passwordController = TextEditingController(); @@ -34,11 +20,14 @@ class _LoginScreenState extends State { FocusNode focusProject = FocusNode(); late AuthenticationViewModel authenticationViewModel; late ProjectViewModel projectViewModel; + ChatCallProvider? callProv; @override Widget build(BuildContext context) { authenticationViewModel = Provider.of(context); projectViewModel = Provider.of(context); + callProv = Provider.of(context); + return AppScaffold( isShowAppBar: false, backgroundColor: HexColor('#F8F8F8'), @@ -174,7 +163,35 @@ class _LoginScreenState extends State { ), SizedBox( height: 25, - ) + ), + // AppButton( + // title: "Call Test", + // color: AppGlobal.appGreenColor, + // fontWeight: FontWeight.w600, + // disabled: false, + // onPressed: () async { + // Future micPer = Permission.microphone.request(); + // Future camPer = Permission.camera.request(); + // if (await micPer.isGranted && await camPer.isGranted) { + // await callProv!.buildHubConnection(); + // IosCallPayload _iosCallPayload = IosCallPayload( + // uuid: "342h8f=-few-3f23rsd-s-f32-r--sd-3rt-2", incomingCallerId: "341682", incomingCallReciverId: "266642", incomingCallerName: "Aamir.Muhammad", incomingCallType: "video"); + // + // Future.delayed(Duration(seconds: 2), () { + // MaterialPageRoute pageRoute = MaterialPageRoute( + // builder: (BuildContext context) => StartCallPage( + // payload: _iosCallPayload, + // ), + // ); + // Navigator.push(context, pageRoute); + // }); + // } else if (await micPer.isDenied) { + // micPer = Permission.microphone.request(); + // } else if (await camPer.isDenied) { + // camPer = Permission.camera.request(); + // } + // }, + // ), ], ), ), diff --git a/lib/voipcall/app_state.dart b/lib/voipcall/app_state.dart new file mode 100644 index 00000000..eba3d96d --- /dev/null +++ b/lib/voipcall/app_state.dart @@ -0,0 +1,46 @@ +import 'package:doctor_app_flutter/voipcall/model/get_user_login_token.dart'; + +class AppState { + static final AppState _instance = AppState._internal(); + + AppState._internal(); + + factory AppState() => _instance; + + bool isLogged = false; + + set setLogged(v) => isLogged = v; + + bool get getIsLogged => isLogged; + + // Calling + UserAutoLoginModel? chatDetails; + + set setchatUserDetails(UserAutoLoginModel details) => chatDetails = details; + + UserAutoLoginModel get getchatUserDetails => chatDetails!; + + String _iosVoipPlayerID = ""; + + String get iosVoipPlayerID => _iosVoipPlayerID; + + set setiosVoipPlayerID(String value) { + _iosVoipPlayerID = value; + } + + bool _isUserOnline = false; + + bool get getisUserOnline => _isUserOnline; + + set setisUserOnline(bool value) { + _isUserOnline = value; + } + + bool _isBackgroundCall = false; + + bool get isBackgroundCall => _isBackgroundCall; + + set isBackgroundCall(bool value) { + _isBackgroundCall = value; + } +} diff --git a/lib/voipcall/call/chat_incoming_call_screen.dart b/lib/voipcall/call/chat_incoming_call_screen.dart new file mode 100644 index 00000000..7adf33d3 --- /dev/null +++ b/lib/voipcall/call/chat_incoming_call_screen.dart @@ -0,0 +1,596 @@ +import 'dart:core'; +import 'dart:io'; +import 'dart:ui'; + +import 'package:doctor_app_flutter/voipcall/app_state.dart'; +import 'package:doctor_app_flutter/voipcall/consts.dart'; +import 'package:doctor_app_flutter/voipcall/model/call.dart'; +import 'package:doctor_app_flutter/voipcall/model/get_single_user_chat_call.dart'; +import 'package:doctor_app_flutter/voipcall/model/get_user_login_token.dart'; +import 'package:doctor_app_flutter/voipcall/model/incoming_call_model.dart'; +import 'package:doctor_app_flutter/voipcall/provider/chat_call_provider.dart'; +import 'package:draggable_widget/draggable_widget.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:provider/provider.dart'; + +bool isCallConnected = false; + +class StartCallPage extends StatefulWidget { + IosCallPayload? payload; + + StartCallPage({this.payload}); + + @override + _StartCallPageState createState() => _StartCallPageState(); +} + +class _StartCallPageState extends State { + DragController dragController = DragController(); + ChatCallProvider? cProv; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + void startCall() async { + IncomingCallModel? sessionData; + // dynamic calls = await FlutterCallkitIncoming.activeCalls(); + // if (calls.isNotEmpty) { + // sessionData.extra.callerDetails = {"id":341682,"userName":"Aamir.Muhammad","email":"Aamir.Muhammad@cloudsolutions.com.sa","phone":null,"title":"Aamir Saleem Ahmad Dost Muhammad","token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIzNDE2ODIiLCJlbWFpbCI6IkFhbWlyLk11aGFtbWFkQGNsb3Vkc29sdXRpb25zLmNvbS5zYSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvdXNlcmRhdGEiOiJBYW1pci5NdWhhbW1hZCIsIm5iZiI6MTcwNzIwNTAyMywiZXhwIjoxNzA3MjkxNDIzLCJpYXQiOjE3MDcyMDUwMjN9.Zz2bcsMwiLAAWGZh2E_p5Qs6ItMt0JHhDFI_0DLdggCQtt6HHcT4n8nvqMZx-uYr3jLwqhA_IAjTuaCeSE15sw","isDomainUser":true,"isActiveCode":false,"encryptedUserId":"nn7RkYzbnQc=","encryptedUserName":"/QruOyp4QMcmNXKZlljUgg=="}; + // sessionData = IncomingCallModel.fromRawJson(jsonEncode(calls[0])); + if (kDebugMode) { + print(sessionData!.toRawJson()); + } + if (cProv!.isUserOnline) { + AppState().isBackgroundCall = true; + if (kDebugMode) { + print("====== Processing Incoming Call in Online State ========="); + } + await cProv!.startIncomingCallViaKit(inCallData: sessionData!.extra!.callerDetails!.toJson(), isVCall: sessionData.extra!.callType == "video" ? true : false); + cProv!.init(); + isCallConnected = true; + } else { + AppState().isBackgroundCall = true; + if (kDebugMode) { + print("====== Processing Incoming Call ========="); + } + // cProv.isUserOnline = provider.isUserOnline; + await cProv!.startIncomingCallViaKit(inCallData: sessionData!.extra!.callerDetails!.toJson(), isVCall: sessionData!.extra!.callType == "video" ? true : false); + try { + AppState().setchatUserDetails = UserAutoLoginModel(response: Response.fromJson(sessionData.extra!.loginDetails!.toJson()), errorResponses: null); + await cProv!.buildHubConnection().whenComplete(() { + cProv!.init(); + isCallConnected = true; + }); + } catch (e) { + print(e); + } + } + //} + + // cProv.startRecording(); + } + + void startIosCall() async { + IosCallPayload iosCallPayload = widget.payload!; + var userID = iosCallPayload.incomingCallReciverId; + var callType = iosCallPayload.incomingCallType; + SingleUserChatModel inCallData = SingleUserChatModel( + targetUserName: iosCallPayload.incomingCallerName, + chatEventId: 3, + targetUserId: int.parse(iosCallPayload.incomingCallerId!), + currentUserId: int.parse(userID.toString()), + ); + if (kDebugMode) { + print("====== Processing Incoming Call ========="); + } + AppState().isBackgroundCall = true; + await cProv!.startIncomingCallViaKit(inCallData: inCallData.toJson(), isVCall: callType == "video" ? true : false); + try { + AppState().setchatUserDetails = UserAutoLoginModel(response: AppState().chatDetails!.response, errorResponses: null); + await cProv!.buildHubConnection().whenComplete(() { + cProv!.init(); + isCallConnected = true; + }); + } catch (e) { + print(e); + } + } + + @override + Widget build(BuildContext context) { + cProv = context.read(); + if (!cProv!.isOutGoingCall) { + if (Platform.isAndroid) { + startCall(); + } else if (Platform.isIOS) { + cProv!.buildHubConnection(); + startIosCall(); + } + } + + return Scaffold( + extendBody: true, + body: Consumer( + builder: (BuildContext context, ChatCallProvider prov, Widget? child) { + return prov.isIncomingCallLoader + ? const SizedBox( + width: double.infinity, + height: double.infinity, + child: Center(child: CircularProgressIndicator()), + ) + : prov.isIncomingCall + ? Container( + width: double.infinity, + height: double.infinity, + color: Colors.black, + child: Stack( + alignment: FractionalOffset.center, + children: [ + if (!prov.isAudioCall && prov.isVideoCall) + RTCVideoView( + prov.remoteRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + key: const Key('remote'), + ), + if (prov.isVideoCall) + DraggableWidget( + bottomMargin: 20, + topMargin: 40, + intialVisibility: true, + horizontalSpace: 20, + shadowBorderRadius: 50, + initialPosition: AnchoringPosition.topLeft, + dragController: dragController, + normalShadow: const BoxShadow(spreadRadius: 0.0, blurRadius: 0.0), + draggingShadow: const BoxShadow(spreadRadius: 0.0, blurRadius: 0.0), + child: SizedBox( + height: 200, + width: 140, + child: RTCVideoView( + prov.localVideoRenderer!, + mirror: true, + // filterQuality: FilterQuality.high, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), + ), + ), + if (!prov.isVideoCall) + Positioned.fill( + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), + child: Container( + decoration: BoxDecoration( + color: MyColors.grey57Color.withOpacity( + 0.3, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + SizedBox( + height: 40, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.all(21.0), + child: Container( + margin: const EdgeInsets.only( + left: 10.0, + right: 10.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SvgPicture.asset( + "assets/images/user.svg", + height: 70, + width: 70, + fit: BoxFit.cover, + ), + SizedBox( + height: 10, + ), + Text( + prov.incomingCallData!.targetUserName ?? "", + style: const TextStyle( + fontSize: 21, + decoration: TextDecoration.none, + fontWeight: FontWeight.bold, + color: MyColors.white, + letterSpacing: -1.26, + height: 23 / 12, + ), + ), + const Text( + "On Call", + style: TextStyle( + fontSize: 16, + decoration: TextDecoration.none, + fontWeight: FontWeight.w600, + color: Color( + 0xffC6C6C6, + ), + letterSpacing: -0.48, + height: 23 / 24, + ), + ), + const SizedBox( + height: 2, + ), + ], + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + padding: const EdgeInsets.only( + bottom: 20, + left: 40, + right: 40, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // if (provider.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.loudOn(); + }, + elevation: 2.0, + fillColor: prov.isLoudSpeaker ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.volume_up, + color: MyColors.white, + size: 30.0, + ), + ), + if (prov.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.camOff(); + }, + elevation: 2.0, + fillColor: prov.isCamOff ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isCamOff ? Icons.videocam_off : Icons.videocam, + color: MyColors.white, + size: 30.0, + ), + ), + if (prov.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.switchCamera(); + }, + elevation: 2.0, + fillColor: prov.isFrontCamera ? Colors.grey : MyColors.textMixColor, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isFrontCamera ? Icons.switch_camera_outlined : Icons.switch_camera, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.micOff(); + }, + elevation: 2.0, + fillColor: prov.isMicOff ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isMicOff ? Icons.mic_off : Icons.mic, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.endCall(isUserOnline: prov.isUserOnline).then((bool value) { + if (value) { + Navigator.of(context).pop(); + } + }); + }, + elevation: 2.0, + fillColor: MyColors.redA3Color, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.call_end, + color: MyColors.white, + size: 30.0, + ), + ), + ], + ), + ), + ), + ], + ), + ) + : prov.isOutGoingCall + ? Container( + width: double.infinity, + height: double.infinity, + color: Colors.black, + child: Stack( + alignment: FractionalOffset.center, + children: [ + if (!prov.isAudioCall && prov.isVideoCall) + RTCVideoView( + prov.remoteRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + key: const Key('remote'), + ), + if (prov.isVideoCall) + DraggableWidget( + bottomMargin: 20, + topMargin: 40, + intialVisibility: true, + horizontalSpace: 20, + shadowBorderRadius: 50, + initialPosition: AnchoringPosition.topLeft, + dragController: dragController, + normalShadow: const BoxShadow(spreadRadius: 0.0, blurRadius: 0.0), + draggingShadow: const BoxShadow(spreadRadius: 0.0, blurRadius: 0.0), + child: SizedBox( + height: 200, + width: 140, + child: RTCVideoView( + prov.localVideoRenderer!, + mirror: true, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), + ), + ), + if (!prov.isVideoCall) + Positioned.fill( + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), + child: Container( + decoration: BoxDecoration( + color: MyColors.grey57Color.withOpacity( + 0.3, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + SizedBox( + height: 40, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.all(21.0), + child: Container( + margin: const EdgeInsets.only( + left: 10.0, + right: 10.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SvgPicture.asset( + "assets/images/user.svg", + height: 70, + width: 70, + fit: BoxFit.cover, + ), + SizedBox( + height: 10, + ), + Text( + prov.outGoingCallData!.receiverName!, + style: const TextStyle( + fontSize: 21, + decoration: TextDecoration.none, + fontWeight: FontWeight.bold, + color: MyColors.white, + letterSpacing: -1.26, + height: 23 / 12, + ), + ), + const Text( + "On Call", + style: TextStyle( + fontSize: 16, + decoration: TextDecoration.none, + fontWeight: FontWeight.w600, + color: Color( + 0xffC6C6C6, + ), + letterSpacing: -0.48, + height: 23 / 24, + ), + ), + const SizedBox( + height: 2, + ), + ], + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + padding: const EdgeInsets.only( + bottom: 20, + left: 40, + right: 40, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // if (provider.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.loudOn(); + }, + elevation: 2.0, + fillColor: prov.isLoudSpeaker ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.volume_up, + color: MyColors.white, + size: 30.0, + ), + ), + + if (prov.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.camOff(); + }, + elevation: 2.0, + fillColor: prov.isCamOff ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isCamOff ? Icons.videocam_off : Icons.videocam, + color: MyColors.white, + size: 30.0, + ), + ), + if (prov.isVideoCall) + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.switchCamera(); + }, + elevation: 2.0, + fillColor: prov.isFrontCamera ? Colors.grey : MyColors.textMixColor, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isFrontCamera ? Icons.switch_camera_outlined : Icons.switch_camera, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.micOff(); + }, + elevation: 2.0, + fillColor: prov.isMicOff ? MyColors.textMixColor : Colors.grey, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: Icon( + prov.isMicOff ? Icons.mic_off : Icons.mic, + color: MyColors.white, + size: 30.0, + ), + ), + RawMaterialButton( + constraints: const BoxConstraints(), + onPressed: () { + prov.endCall(isUserOnline: prov.isUserOnline).then( + (bool value) { + if (value) { + Navigator.of(context).pop(); + } + }, + ); + }, + elevation: 2.0, + fillColor: MyColors.redA3Color, + padding: const EdgeInsets.all( + 10.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.call_end, + color: MyColors.white, + size: 30.0, + ), + ), + ], + ), + ), + ), + ], + ), + ) + : const SizedBox(); + }, + ), + ); + } +} diff --git a/lib/voipcall/call/chat_outgoing_call_screen.dart b/lib/voipcall/call/chat_outgoing_call_screen.dart new file mode 100644 index 00000000..8f54d15e --- /dev/null +++ b/lib/voipcall/call/chat_outgoing_call_screen.dart @@ -0,0 +1,200 @@ +import 'dart:ui'; + +import 'package:doctor_app_flutter/voipcall/consts.dart'; +import 'package:doctor_app_flutter/voipcall/model/call.dart'; +import 'package:doctor_app_flutter/voipcall/provider/chat_call_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:provider/provider.dart'; + +class OutGoingCall extends StatefulWidget { + CallDataModel? outGoingCallData; + bool isVideoCall; + + OutGoingCall({Key? key, this.outGoingCallData, this.isVideoCall = false}) : super(key: key); + + @override + _OutGoingCallState createState() => _OutGoingCallState(); +} + +class _OutGoingCallState extends State { + ChatCallProvider? callProvider; + + //late ChatProviderModel chatProvider; + bool loader = true; + + @override + void initState() { + super.initState(); + } + + Future init() async { + // widget.isVideoCall ? callProvider!.isVideoCall = true : callProvider!.isVideoCall = false; + widget.isVideoCall = true; + callProvider!.isOutGoingCall = true; + // IosCallPayload payload = IosCallPayload( + // incomingCallerId: widget.outGoingCallData.callerId.toString(), + // incomingCallerName: widget.outGoingCallData.callerName, + // incomingCallReciverId: widget.outGoingCallData.receiverId.toString(), + // incomingCallType: widget.outGoingCallData.callType, + // uuid: "", + // callData: widget.outGoingCallData.toRawJson()); + // await Utils.saveStringFromPrefs("iosCallPayload", jsonEncode(payload)); + await callProvider!.initLocalCamera(callData: widget.outGoingCallData); + loader = false; + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + callProvider = Provider.of(context, listen: false); + init(); + return Scaffold( + body: Consumer(builder: (BuildContext context, ChatCallProvider chatcp, Widget? child) { + return loader + ? const Center( + child: CircularProgressIndicator(), + ) + : Stack( + alignment: FractionalOffset.center, + children: [ + if (chatcp.isVideoCall) + Positioned.fill( + child: RTCVideoView( + chatcp.localVideoRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), + ), + Positioned.fill( + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), + child: Container( + decoration: BoxDecoration( + color: MyColors.grey57Color.withOpacity( + 0.3, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + SizedBox( + height: 40, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.all(21.0), + child: Container( + margin: const EdgeInsets.only( + left: 10.0, + right: 10.0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SvgPicture.asset( + "assets/images/user.svg", + height: 70, + width: 70, + fit: BoxFit.cover, + ), + SizedBox( + height: 10, + ), + Text( + widget.outGoingCallData!.receiverName.toString().replaceAll(".", " "), + style: const TextStyle( + fontSize: 21, + fontWeight: FontWeight.bold, + color: MyColors.white, + letterSpacing: -1.26, + height: 23 / 12, + ), + ), + const Text( + "Ringing...", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color( + 0xffC6C6C6, + ), + letterSpacing: -0.48, + height: 23 / 24, + ), + ), + const SizedBox( + height: 2, + ), + ], + ), + ), + ), + ], + ), + const Spacer(), + Container( + margin: const EdgeInsets.only( + bottom: 70.0, + left: 49, + right: 49, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RawMaterialButton( + onPressed: () { + chatcp.endCall(isUserOnline: chatcp.isUserOnline).then((bool value) { + if (value) { + Navigator.of(context).pop(); + } + }); + }, + elevation: 2.0, + fillColor: MyColors.redA3Color, + padding: const EdgeInsets.all( + 15.0, + ), + shape: const CircleBorder(), + child: const Icon( + Icons.call_end, + color: MyColors.white, + size: 35.0, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ], + ); + }), + ); + } + + BoxDecoration cardRadius(double radius, {Color? color, double? elevation}) { + return BoxDecoration( + shape: BoxShape.rectangle, + color: color ?? Colors.white, + borderRadius: BorderRadius.all(Radius.circular(radius)), + boxShadow: [BoxShadow(color: const Color(0xff000000).withOpacity(.05), blurRadius: elevation ?? 27, offset: const Offset(-2, 3))], + ); + } +} diff --git a/lib/voipcall/call/draggable_cam_screen.dart b/lib/voipcall/call/draggable_cam_screen.dart new file mode 100644 index 00000000..14f0fc8a --- /dev/null +++ b/lib/voipcall/call/draggable_cam_screen.dart @@ -0,0 +1,171 @@ +// import 'dart:async'; +// import 'dart:io'; +// import 'package:flutter/material.dart'; +// +// class DraggableCam extends StatefulWidget { +// //final Size availableScreenSize; +// final Widget child; +// final double scaleFactor; +// // final Stream onButtonBarVisible; +// // final Stream onButtonBarHeight; +// +// const DraggableCam({ +// Key? key, +// //@required this.availableScreenSize, +// required 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 { +// bool _isButtonBarVisible = true; +// double _buttonBarHeight = 0; +// late double _width; +// late double _height; +// late double _top; +// late double _left; +// late double _viewPaddingTop; +// late double _viewPaddingBottom; +// final double _padding = 8.0; +// final Duration _duration300ms = const Duration(milliseconds: 300); +// final Duration _duration0ms = const Duration(milliseconds: 0); +// late Duration _duration; +// late StreamSubscription _streamSubscription; +// late 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() { +// var 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; +// } +// }); +// } +// } diff --git a/lib/voipcall/consts.dart b/lib/voipcall/consts.dart new file mode 100644 index 00000000..2c9de7f9 --- /dev/null +++ b/lib/voipcall/consts.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ApiConsts { + static String chatServerBaseUrl = "https://apiderichat.hmg.com/"; + static String chatServerBaseApiUrl = chatServerBaseUrl + "api/"; + static String chatLoginTokenUrl = chatServerBaseApiUrl + "user/"; + static String chatHubConnectionUrl = chatServerBaseUrl + "ConnectionChatHub"; +} + +class AppSharedPrefs { + SharedPreferences? sharedPref; + + init() async { + sharedPref = await SharedPreferences.getInstance(); + } + + Future setStringFromPrefs(String key, String value) async { + sharedPref!.setString(key, value); + } + + Future getStringFromPrefs(String key) async { + return await sharedPref!.getString(key) ?? ""; + } +} + +class MyColors { + static const Color backgroundColor = Color(0xffF8F8F8); + static const Color grey41Color = Color(0xff414141); + static const Color grey57Color = Color(0xff575757); + static const Color grey67Color = Color(0xff676767); + static const Color grey77Color = Color(0xff777777); + static const Color greyF7Color = Color(0xffF7F7F7); + static const Color white = Color(0xffffffff); + static const Color redA3Color = Color(0xffCA3332); + static const Color textMixColor = Color(0xff2BB8A6); +} diff --git a/lib/voipcall/model/call.dart b/lib/voipcall/model/call.dart new file mode 100644 index 00000000..4848b4a3 --- /dev/null +++ b/lib/voipcall/model/call.dart @@ -0,0 +1,164 @@ +// To parse this JSON data, do +// +// final callDataModel = callDataModelFromJson(jsonString); + +import 'dart:convert'; + +class CallDataModel { + CallDataModel({ + this.callerId, + this.callerName, + this.callerEmail, + this.callerTitle, + this.callerPhone, + this.receiverId, + this.receiverName, + this.receiverEmail, + this.receiverTitle, + this.receiverPhone, + this.title, + this.callType, + }); + + int? callerId; + String? callerName; + String? callerEmail; + String? callerTitle; + dynamic? callerPhone; + int? receiverId; + String? receiverName; + String? receiverEmail; + dynamic? receiverTitle; + dynamic? receiverPhone; + String? title; + String? callType; + + factory CallDataModel.fromRawJson(String str) => CallDataModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory CallDataModel.fromJson(Map json) => CallDataModel( + callerId: json["callerID"], + callerName: json["callerName"], + callerEmail: json["callerEmail"], + callerTitle: json["callerTitle"], + callerPhone: json["callerPhone"], + receiverId: json["receiverID"], + receiverName: json["receiverName"], + receiverEmail: json["receiverEmail"], + receiverTitle: json["receiverTitle"], + receiverPhone: json["receiverPhone"], + title: json["title"], + callType: json["callType"], + ); + + Map toJson() => { + "callerID": callerId, + "callerName": callerName, + "callerEmail": callerEmail, + "callerTitle": callerTitle, + "callerPhone": callerPhone, + "receiverID": receiverId, + "receiverName": receiverName, + "receiverEmail": receiverEmail, + "receiverTitle": receiverTitle, + "receiverPhone": receiverPhone, + "title": title, + "callType": callType, + }; +} + +// To parse this JSON data, do +// +// final callSessionPayLoad = callSessionPayLoadFromJson(jsonString); + +class CallSessionPayLoad { + CallSessionPayLoad({ + this.target, + this.caller, + this.sdp, + }); + + int? target; + int? caller; + Sdp? sdp; + + factory CallSessionPayLoad.fromRawJson(String str) => CallSessionPayLoad.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory CallSessionPayLoad.fromJson(Map json) => CallSessionPayLoad( + target: json["target"], + caller: json["caller"], + sdp: json["sdp"] == null ? null : Sdp.fromJson(json["sdp"]), + ); + + Map toJson() => { + "target": target, + "caller": caller, + "sdp": sdp?.toJson(), + }; +} + +class Sdp { + Sdp({ + this.type, + this.sdp, + }); + + String? type; + String? sdp; + + factory Sdp.fromRawJson(String str) => Sdp.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Sdp.fromJson(Map json) => Sdp( + type: json["type"], + sdp: json["sdp"], + ); + + Map toJson() => { + "type": type, + "sdp": sdp, + }; +} + +// final iosCallPayload = iosCallPayloadFromJson(jsonString); + +class IosCallPayload { + String? incomingCallType; + String? incomingCallerId; + String? incomingCallReciverId; + String? incomingCallerName; + String? callData; + String? uuid; + + IosCallPayload({ + this.incomingCallType, + this.incomingCallerId, + this.incomingCallReciverId, + this.incomingCallerName, + this.callData, + this.uuid, + }); + + factory IosCallPayload.fromRawJson(String str) => IosCallPayload.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory IosCallPayload.fromJson(Map json) => IosCallPayload( + incomingCallType: json["incoming_call_type"], + incomingCallerId: json["incoming_caller_id"], + incomingCallerName: json["incoming_caller_name"], + incomingCallReciverId: null, + uuid: json["uuid"], + ); + + Map toJson() => { + "incoming_call_type": incomingCallType, + "incoming_caller_id": incomingCallerId, + "incoming_caller_name": incomingCallerName, + "uuid": uuid, + }; +} diff --git a/lib/voipcall/model/get_single_user_chat_call.dart b/lib/voipcall/model/get_single_user_chat_call.dart new file mode 100644 index 00000000..939743d0 --- /dev/null +++ b/lib/voipcall/model/get_single_user_chat_call.dart @@ -0,0 +1,207 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:just_audio/just_audio.dart'; + +List singleUserChatModelFromJson(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); + +String singleUserChatModelToJson(List data) => json.encode(List.from(data.map((x) => x.toJson()))); + +class SingleUserChatModel { + SingleUserChatModel( + {this.userChatHistoryId, + this.userChatHistoryLineId, + this.contant, + this.contantNo, + this.currentUserId, + this.currentUserName, + this.targetUserId, + this.targetUserName, + this.encryptedTargetUserId, + this.encryptedTargetUserName, + this.currentUserEmail, + this.targetUserEmail, + this.chatEventId, + this.fileTypeId, + this.isSeen, + this.isDelivered, + this.createdDate, + this.chatSource, + this.conversationId, + this.fileTypeResponse, + this.userChatReplyResponse, + this.isReplied, + this.isImageLoaded, + this.image, + this.voice, + this.voiceController}); + + int? userChatHistoryId; + int? userChatHistoryLineId; + String? contant; + String? contantNo; + int? currentUserId; + String? currentUserName; + String? currentUserEmail; + int? targetUserId; + String? targetUserName; + String? targetUserEmail; + String? encryptedTargetUserId; + String? encryptedTargetUserName; + int? chatEventId; + dynamic fileTypeId; + bool? isSeen; + bool? isDelivered; + DateTime? createdDate; + int? chatSource; + String? conversationId; + FileTypeResponse? fileTypeResponse; + UserChatReplyResponse? userChatReplyResponse; + bool? isReplied; + bool? isImageLoaded; + Uint8List? image; + File? voice; + AudioPlayer? voiceController; + + factory SingleUserChatModel.fromJson(Map json) => SingleUserChatModel( + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], + userChatHistoryLineId: json["userChatHistoryLineId"] == null ? null : json["userChatHistoryLineId"], + contant: json["contant"] == null ? null : json["contant"], + contantNo: json["contantNo"] == null ? null : json["contantNo"], + currentUserId: json["currentUserId"] == null ? null : json["currentUserId"], + currentUserName: json["currentUserName"] == null ? null : json["currentUserName"], + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], + targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], + targetUserEmail: json["targetUserEmail"] == null ? null : json["targetUserEmail"], + currentUserEmail: json["currentUserEmail"] == null ? null : json["currentUserEmail"], + encryptedTargetUserId: json["encryptedTargetUserId"] == null ? null : json["encryptedTargetUserId"], + encryptedTargetUserName: json["encryptedTargetUserName"] == null ? null : json["encryptedTargetUserName"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], + fileTypeId: json["fileTypeId"], + isSeen: json["isSeen"] == null ? null : json["isSeen"], + isDelivered: json["isDelivered"] == null ? null : json["isDelivered"], + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + chatSource: json["chatSource"] == null ? null : json["chatSource"], + conversationId: json["conversationId"] == null ? null : json["conversationId"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + userChatReplyResponse: json["userChatReplyResponse"] == null ? null : UserChatReplyResponse.fromJson(json["userChatReplyResponse"]), + isReplied: false, + isImageLoaded: false, + image: null, + voice: null, + voiceController: json["fileTypeId"] == 13 ? AudioPlayer() : null); + + Map toJson() => { + "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, + "userChatHistoryLineId": userChatHistoryLineId == null ? null : userChatHistoryLineId, + "contant": contant == null ? null : contant, + "contantNo": contantNo == null ? null : contantNo, + "currentUserId": currentUserId == null ? null : currentUserId, + "currentUserName": currentUserName == null ? null : currentUserName, + "targetUserId": targetUserId == null ? null : targetUserId, + "targetUserName": targetUserName == null ? null : targetUserName, + "encryptedTargetUserId": encryptedTargetUserId == null ? null : encryptedTargetUserId, + "encryptedTargetUserName": encryptedTargetUserName == null ? null : encryptedTargetUserName, + "currentUserEmail": currentUserEmail == null ? null : currentUserEmail, + "targetUserEmail": targetUserEmail == null ? null : targetUserEmail, + "chatEventId": chatEventId == null ? null : chatEventId, + "fileTypeId": fileTypeId, + "isSeen": isSeen == null ? null : isSeen, + "isDelivered": isDelivered == null ? null : isDelivered, + "createdDate": createdDate == null ? null : createdDate!.toIso8601String(), + "chatSource": chatSource == null ? null : chatSource, + "conversationId": conversationId == null ? null : conversationId, + "fileTypeResponse": fileTypeResponse == null ? null : fileTypeResponse!.toJson(), + "userChatReplyResponse": userChatReplyResponse == null ? null : userChatReplyResponse!.toJson(), + }; +} + +class FileTypeResponse { + FileTypeResponse({ + this.fileTypeId, + this.fileTypeName, + this.fileTypeDescription, + this.fileKind, + this.fileName, + }); + + int? fileTypeId; + dynamic fileTypeName; + dynamic fileTypeDescription; + dynamic fileKind; + dynamic fileName; + + factory FileTypeResponse.fromJson(Map json) => FileTypeResponse( + fileTypeId: json["fileTypeId"] == null ? null : json["fileTypeId"], + fileTypeName: json["fileTypeName"], + fileTypeDescription: json["fileTypeDescription"], + fileKind: json["fileKind"], + fileName: json["fileName"], + ); + + Map toJson() => { + "fileTypeId": fileTypeId == null ? null : fileTypeId, + "fileTypeName": fileTypeName, + "fileTypeDescription": fileTypeDescription, + "fileKind": fileKind, + "fileName": fileName, + }; +} + +class UserChatReplyResponse { + UserChatReplyResponse( + {this.userChatHistoryId, + this.chatEventId, + this.contant, + this.contantNo, + this.fileTypeId, + this.createdDate, + this.targetUserId, + this.targetUserName, + this.fileTypeResponse, + this.isImageLoaded, + this.image, + this.voice}); + + int? userChatHistoryId; + int? chatEventId; + String? contant; + String? contantNo; + dynamic fileTypeId; + DateTime? createdDate; + int? targetUserId; + String? targetUserName; + FileTypeResponse? fileTypeResponse; + bool? isImageLoaded; + Uint8List? image; + Uint8List? voice; + + factory UserChatReplyResponse.fromJson(Map json) => UserChatReplyResponse( + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], + contant: json["contant"] == null ? null : json["contant"], + contantNo: json["contantNo"] == null ? null : json["contantNo"], + fileTypeId: json["fileTypeId"], + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], + targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + isImageLoaded: false, + image: null, + voice: null, + ); + + Map toJson() => { + "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, + "chatEventId": chatEventId == null ? null : chatEventId, + "contant": contant == null ? null : contant, + "contantNo": contantNo == null ? null : contantNo, + "fileTypeId": fileTypeId, + "createdDate": createdDate == null ? null : createdDate!.toIso8601String(), + "targetUserId": targetUserId == null ? null : targetUserId, + "targetUserName": targetUserName == null ? null : targetUserName, + "fileTypeResponse": fileTypeResponse == null ? null : fileTypeResponse!.toJson(), + }; +} diff --git a/lib/voipcall/model/get_user_login_token.dart b/lib/voipcall/model/get_user_login_token.dart new file mode 100644 index 00000000..8d55461f --- /dev/null +++ b/lib/voipcall/model/get_user_login_token.dart @@ -0,0 +1,97 @@ +import 'dart:convert'; + +UserAutoLoginModel userAutoLoginModelFromJson(String str) => UserAutoLoginModel.fromJson(json.decode(str)); + +String userAutoLoginModelToJson(UserAutoLoginModel data) => json.encode(data.toJson()); + +class UserAutoLoginModel { + UserAutoLoginModel({ + this.response, + this.errorResponses, + }); + + Response? response; + List? errorResponses; + + factory UserAutoLoginModel.fromJson(Map json) => UserAutoLoginModel( + response: json["response"] == null ? null : Response.fromJson(json["response"]), + errorResponses: json["errorResponses"] == null ? null : List.from(json["errorResponses"].map((x) => ErrorResponse.fromJson(x))), + ); + + Map toJson() => { + "response": response == null ? null : response!.toJson(), + "errorResponses": errorResponses == null ? null : List.from(errorResponses!.map((x) => x.toJson())), + }; +} + +class Response { + Response({ + this.id, + this.userName, + this.email, + this.phone, + this.title, + this.token, + this.isDomainUser, + this.isActiveCode, + this.encryptedUserId, + this.encryptedUserName, + }); + + int? id; + String? userName; + String? email; + String? phone; + String? title; + String? token; + bool? isDomainUser; + bool? isActiveCode; + String? encryptedUserId; + String? encryptedUserName; + + factory Response.fromJson(Map json) => Response( + id: json["id"] == null ? null : json["id"], + userName: json["userName"] == null ? null : json["userName"], + email: json["email"] == null ? null : json["email"], + phone: json["phone"] == null ? null : json["phone"], + title: json["title"] == null ? null : json["title"], + token: json["token"] == null ? null : json["token"], + isDomainUser: json["isDomainUser"] == null ? null : json["isDomainUser"], + isActiveCode: json["isActiveCode"] == null ? null : json["isActiveCode"], + encryptedUserId: json["encryptedUserId"] == null ? null : json["encryptedUserId"], + encryptedUserName: json["encryptedUserName"] == null ? null : json["encryptedUserName"], + ); + + Map toJson() => { + "id": id == null ? null : id, + "userName": userName == null ? null : userName, + "email": email == null ? null : email, + "phone": phone == null ? null : phone, + "title": title == null ? null : title, + "token": token == null ? null : token, + "isDomainUser": isDomainUser == null ? null : isDomainUser, + "isActiveCode": isActiveCode == null ? null : isActiveCode, + "encryptedUserId": encryptedUserId == null ? null : encryptedUserId, + "encryptedUserName": encryptedUserName == null ? null : encryptedUserName, + }; +} + +class ErrorResponse { + ErrorResponse({ + this.fieldName, + this.message, + }); + + String? fieldName; + String? message; + + factory ErrorResponse.fromJson(Map json) => ErrorResponse( + fieldName: json["fieldName"] == null ? null : json["fieldName"], + message: json["message"] == null ? null : json["message"], + ); + + Map toJson() => { + "fieldName": fieldName == null ? null : fieldName, + "message": message == null ? null : message, + }; +} diff --git a/lib/voipcall/model/incoming_call_model.dart b/lib/voipcall/model/incoming_call_model.dart new file mode 100644 index 00000000..94945052 --- /dev/null +++ b/lib/voipcall/model/incoming_call_model.dart @@ -0,0 +1,333 @@ +// To parse this JSON data, do +// +// final incomingCallModel = incomingCallModelFromJson(jsonString); + +import 'dart:convert'; + +class IncomingCallModel { + String? actionColor; + String? appName; + Args? args; + String? avatar; + String? backgroundColor; + String? backgroundUrl; + int? duration; + Extra? extra; + String? from; + String? handle; + Args? headers; + String? id; + bool? isAccepted; + bool? isCustomNotification; + bool? isCustomSmallExNotification; + bool? isShowCallback; + bool? isShowLogo; + bool? isShowMissedCallNotification; + String? nameCaller; + String? ringtonePath; + String? textAccept; + String? textCallback; + String? textDecline; + String? textMissedCall; + int? type; + String? uuid; + + IncomingCallModel({ + this.actionColor, + this.appName, + this.args, + this.avatar, + this.backgroundColor, + this.backgroundUrl, + this.duration, + this.extra, + this.from, + this.handle, + this.headers, + this.id, + this.isAccepted, + this.isCustomNotification, + this.isCustomSmallExNotification, + this.isShowCallback, + this.isShowLogo, + this.isShowMissedCallNotification, + this.nameCaller, + this.ringtonePath, + this.textAccept, + this.textCallback, + this.textDecline, + this.textMissedCall, + this.type, + this.uuid, + }); + + factory IncomingCallModel.fromRawJson(String str) => IncomingCallModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory IncomingCallModel.fromJson(Map json) => IncomingCallModel( + actionColor: json["actionColor"], + appName: json["appName"], + args: json["args"] == null ? null : Args.fromJson(json["args"]), + avatar: json["avatar"], + backgroundColor: json["backgroundColor"], + backgroundUrl: json["backgroundUrl"], + duration: json["duration"] == null ? null : json["duration"].toInt(), + extra: json["extra"] == null ? null : Extra.fromJson(json["extra"]), + from: json["from"], + handle: json["handle"], + headers: json["headers"] == null ? null : Args.fromJson(json["headers"]), + id: json["id"], + isAccepted: json["isAccepted"], + isCustomNotification: json["isCustomNotification"], + isCustomSmallExNotification: json["isCustomSmallExNotification"], + isShowCallback: json["isShowCallback"], + isShowLogo: json["isShowLogo"], + isShowMissedCallNotification: json["isShowMissedCallNotification"], + nameCaller: json["nameCaller"], + ringtonePath: json["ringtonePath"], + textAccept: json["textAccept"], + textCallback: json["textCallback"], + textDecline: json["textDecline"], + textMissedCall: json["textMissedCall"], + type: json["type"] == null ? null : json["type"].toInt(), + uuid: json["uuid"], + ); + + Map toJson() => { + "actionColor": actionColor, + "appName": appName, + "args": args?.toJson(), + "avatar": avatar, + "backgroundColor": backgroundColor, + "backgroundUrl": backgroundUrl, + "duration": duration, + "extra": extra?.toJson(), + "from": from, + "handle": handle, + "headers": headers?.toJson(), + "id": id, + "isAccepted": isAccepted, + "isCustomNotification": isCustomNotification, + "isCustomSmallExNotification": isCustomSmallExNotification, + "isShowCallback": isShowCallback, + "isShowLogo": isShowLogo, + "isShowMissedCallNotification": isShowMissedCallNotification, + "nameCaller": nameCaller, + "ringtonePath": ringtonePath, + "textAccept": textAccept, + "textCallback": textCallback, + "textDecline": textDecline, + "textMissedCall": textMissedCall, + "type": type, + "uuid": uuid, + }; +} + +class Args { + Args(); + + factory Args.fromRawJson(String str) => Args.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Args.fromJson(Map json) => Args(); + + Map toJson() => {}; +} + +class Extra { + LoginDetails? loginDetails; + bool? isIncomingCall; + CallerDetails? callerDetails; + String? callType; + + Extra({ + this.loginDetails, + this.isIncomingCall, + this.callerDetails, + this.callType, + }); + + factory Extra.fromRawJson(String str) => Extra.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Extra.fromJson(Map json) => Extra( + loginDetails: json["loginDetails"] == null ? null : LoginDetails.fromJson(json["loginDetails"]), + isIncomingCall: json["isIncomingCall"], + callType: json["callType"], + callerDetails: json["callerDetails"] == null ? null : CallerDetails.fromJson(json["callerDetails"]), + ); + + Map toJson() => { + "loginDetails": loginDetails?.toJson(), + "isIncomingCall": isIncomingCall, + "callType": callType, + "callerDetails": callerDetails?.toJson(), + }; +} + +class CallerDetails { + int? userChatHistoryId; + String? contant; + FileTypeResponse? fileTypeResponse; + String? currentUserName; + String? targetUserEmail; + String? conversationId; + String? encryptedTargetUserId; + int? targetUserId; + bool? isSeen; + int? userChatHistoryLineId; + bool? isDelivered; + String? targetUserName; + int? currentUserId; + DateTime? createdDate; + String? currentUserEmail; + String? contantNo; + int? chatEventId; + String? encryptedTargetUserName; + int? chatSource; + + CallerDetails({ + this.userChatHistoryId, + this.contant, + this.fileTypeResponse, + this.currentUserName, + this.targetUserEmail, + this.conversationId, + this.encryptedTargetUserId, + this.targetUserId, + this.isSeen, + this.userChatHistoryLineId, + this.isDelivered, + this.targetUserName, + this.currentUserId, + this.createdDate, + this.currentUserEmail, + this.contantNo, + this.chatEventId, + this.encryptedTargetUserName, + this.chatSource, + }); + + factory CallerDetails.fromRawJson(String str) => CallerDetails.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory CallerDetails.fromJson(Map json) => CallerDetails( + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"].toInt(), + contant: json["contant"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + currentUserName: json["currentUserName"], + targetUserEmail: json["targetUserEmail"], + conversationId: json["conversationId"], + encryptedTargetUserId: json["encryptedTargetUserId"], + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"].toInt(), + isSeen: json["isSeen"], + userChatHistoryLineId: json["userChatHistoryLineId"] == null ? null : json["userChatHistoryLineId"].toInt(), + isDelivered: json["isDelivered"], + targetUserName: json["targetUserName"], + currentUserId: json["currentUserId"] == null ? null : json["currentUserId"].toInt(), + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + currentUserEmail: json["currentUserEmail"], + contantNo: json["contantNo"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"].toInt(), + encryptedTargetUserName: json["encryptedTargetUserName"], + chatSource: json["chatSource"] == null ? null : json["chatSource"].toInt(), + ); + + Map toJson() => { + "userChatHistoryId": userChatHistoryId, + "contant": contant, + "fileTypeResponse": fileTypeResponse?.toJson(), + "currentUserName": currentUserName, + "targetUserEmail": targetUserEmail, + "conversationId": conversationId, + "encryptedTargetUserId": encryptedTargetUserId, + "targetUserId": targetUserId, + "isSeen": isSeen, + "userChatHistoryLineId": userChatHistoryLineId, + "isDelivered": isDelivered, + "targetUserName": targetUserName, + "currentUserId": currentUserId, + "createdDate": createdDate?.toIso8601String(), + "currentUserEmail": currentUserEmail, + "contantNo": contantNo, + "chatEventId": chatEventId, + "encryptedTargetUserName": encryptedTargetUserName, + "chatSource": chatSource, + }; +} + +class FileTypeResponse { + int? fileTypeId; + + FileTypeResponse({ + this.fileTypeId, + }); + + factory FileTypeResponse.fromRawJson(String str) => FileTypeResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory FileTypeResponse.fromJson(Map json) => FileTypeResponse( + fileTypeId: json["fileTypeId"].toInt(), + ); + + Map toJson() => { + "fileTypeId": fileTypeId, + }; +} + +class LoginDetails { + bool? isActiveCode; + int? id; + String? encryptedUserName; + String? userName; + String? title; + String? encryptedUserId; + String? email; + bool? isDomainUser; + String? token; + + LoginDetails({ + this.isActiveCode, + this.id, + this.encryptedUserName, + this.userName, + this.title, + this.encryptedUserId, + this.email, + this.isDomainUser, + this.token, + }); + + factory LoginDetails.fromRawJson(String str) => LoginDetails.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LoginDetails.fromJson(Map json) => LoginDetails( + isActiveCode: json["isActiveCode"], + id: json["id"] == null ? null : json["id"].toInt(), + encryptedUserName: json["encryptedUserName"], + userName: json["userName"], + title: json["title"], + encryptedUserId: json["encryptedUserId"], + email: json["email"], + isDomainUser: json["isDomainUser"], + token: json["token"], + ); + + Map toJson() => { + "isActiveCode": isActiveCode, + "id": id, + "encryptedUserName": encryptedUserName, + "userName": userName, + "title": title, + "encryptedUserId": encryptedUserId, + "email": email, + "isDomainUser": isDomainUser, + "token": token, + }; +} diff --git a/lib/voipcall/model/webrtc_payloads.dart b/lib/voipcall/model/webrtc_payloads.dart new file mode 100644 index 00000000..b4019419 --- /dev/null +++ b/lib/voipcall/model/webrtc_payloads.dart @@ -0,0 +1,61 @@ +// To parse this JSON data, do +// +// final remoteIceCandidatePayLoad = remoteIceCandidatePayLoadFromJson(jsonString); + +import 'dart:convert'; + +class RemoteIceCandidatePayLoad { + RemoteIceCandidatePayLoad({ + this.target, + this.candidate, + }); + + int? target; + Candidate? candidate; + + factory RemoteIceCandidatePayLoad.fromRawJson(String str) => RemoteIceCandidatePayLoad.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RemoteIceCandidatePayLoad.fromJson(Map json) => RemoteIceCandidatePayLoad( + target: json["target"], + candidate: json["candidate"] == null ? null : Candidate.fromJson(json["candidate"]), + ); + + Map toJson() => { + "target": target, + "candidate": candidate?.toJson(), + }; +} + +class Candidate { + Candidate({ + this.candidate, + this.sdpMid, + this.sdpMLineIndex, + this.usernameFragment, + }); + + String? candidate; + String? sdpMid; + int? sdpMLineIndex; + String? usernameFragment; + + factory Candidate.fromRawJson(String str) => Candidate.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Candidate.fromJson(Map json) => Candidate( + candidate: json["candidate"], + sdpMid: json["sdpMid"], + sdpMLineIndex: json["sdpMLineIndex"], + usernameFragment: json["usernameFragment"], + ); + + Map toJson() => { + "candidate": candidate, + "sdpMid": sdpMid, + "sdpMLineIndex": sdpMLineIndex, + "usernameFragment": usernameFragment, + }; +} diff --git a/lib/voipcall/provider/chat_call_provider.dart b/lib/voipcall/provider/chat_call_provider.dart new file mode 100644 index 00000000..58cf296e --- /dev/null +++ b/lib/voipcall/provider/chat_call_provider.dart @@ -0,0 +1,821 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:doctor_app_flutter/core/service/NavigationService.dart'; +import 'package:doctor_app_flutter/locator.dart'; +import 'package:doctor_app_flutter/routes.dart'; +import 'package:doctor_app_flutter/utils/utils.dart'; +import 'package:doctor_app_flutter/voipcall/app_state.dart'; +import 'package:doctor_app_flutter/voipcall/call/chat_incoming_call_screen.dart'; +import 'package:doctor_app_flutter/voipcall/consts.dart'; +import 'package:doctor_app_flutter/voipcall/model/call.dart'; +import 'package:doctor_app_flutter/voipcall/model/get_single_user_chat_call.dart'; +import 'package:doctor_app_flutter/voipcall/model/get_user_login_token.dart'; +import 'package:doctor_app_flutter/voipcall/model/webrtc_payloads.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:http/http.dart' as http; +import 'package:just_audio/just_audio.dart'; +import 'package:signalr_netcore/signalr_client.dart'; + +class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin { + ///////////////////// Web RTC Video Calling ////////////////////// + // Video Call + HubConnection? chatHubConnection; + RTCPeerConnection? _pc; + + //late ChatProviderModel chatProvModel; + RTCVideoRenderer? localVideoRenderer; + RTCVideoRenderer? remoteRenderer; + + final AudioPlayer player = AudioPlayer(); + MediaStream? _localStream; + CallDataModel? outGoingCallData; + bool isMicOff = false; + bool isLoudSpeaker = false; + bool isCamOff = false; + bool isCallEnded = false; + + // This need to Be Changed to dynamic + bool isVideoCall = true; + bool isAudioCall = false; + bool isCallStarted = false; + bool isFrontCamera = true; + SingleUserChatModel? incomingCallData; + + /// WebRTC Connection Variables + bool isIncomingCallLoader = true; + bool isIncomingCall = false; + bool isOutGoingCall = false; + bool isUserOnline = false; + + List devices = []; + + Future buildHubConnection() async { + try { + chatHubConnection = await getHubConnection(); + await chatHubConnection!.start(); + } catch (e) { + print(e.toString()); + Utils.showErrorToast(e.toString()); + } + + if (kDebugMode) { + print("Hub Conn: Startedddddddd"); + } + registerCallListners(); + } + +//{"id":341682,"userName":"Aamir.Muhammad","email":"Aamir.Muhammad@cloudsolutions.com.sa","phone":null,"title":"Aamir Saleem Ahmad Dost Muhammad","token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIzNDE2ODIiLCJlbWFpbCI6IkFhbWlyLk11aGFtbWFkQGNsb3Vkc29sdXRpb25zLmNvbS5zYSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvdXNlcmRhdGEiOiJBYW1pci5NdWhhbW1hZCIsIm5iZiI6MTcwNzIwNTAyMywiZXhwIjoxNzA3MjkxNDIzLCJpYXQiOjE3MDcyMDUwMjN9.Zz2bcsMwiLAAWGZh2E_p5Qs6ItMt0JHhDFI_0DLdggCQtt6HHcT4n8nvqMZx-uYr3jLwqhA_IAjTuaCeSE15sw","isDomainUser":true,"isActiveCode":false,"encryptedUserId":"nn7RkYzbnQc=","encryptedUserName":"/QruOyp4QMcmNXKZlljUgg=="} + Future getHubConnection() async { + AppState().chatDetails = UserAutoLoginModel( + response: Response.fromJson({ + "id": 266642, + "userName": "Muhamad.Alam", + "email": "Muhamad.Alam@cloudsolutions.com.sa", + "phone": null, + "title": "Muhamad.Alam", + "token": + "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIyNjY2NDIiLCJlbWFpbCI6Ik11aGFtYWQuQWxhbUBjbG91ZHNvbHV0aW9ucy5jb20uc2EiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3VzZXJkYXRhIjoiTXVoYW1hZC5BbGFtIiwibmJmIjoxNzA4MjU2MjU4LCJleHAiOjE3MDgzNDI2NTgsImlhdCI6MTcwODI1NjI1OH0.Ol8VJFhaFMFgcMXpFi8XI38v8dDmh8-tpPaXSWTXQF1yXqPFNtnyN2t_6ar3-N92bS60yZ1w3v4s3Tx7dirfow", + "isDomainUser": true, + "isActiveCode": false, + "encryptedUserId": "8NOEvvO7oi0=", + "encryptedUserName": "YaYBnsXyEusQT8TAOmYQqA==" + }), + ); + + HubConnection hub; + HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + hub = HubConnectionBuilder() + .withUrl(ApiConsts.chatHubConnectionUrl + "?UserId=${AppState().chatDetails!.response!.id}&source=Desktop&access_token=${AppState().chatDetails!.response!.token}", options: httpOp) + .withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); + return hub; + } + + Future makeCall({required String callType}) async { + Map json = { + "callerID": AppState().chatDetails!.response!.id, + "callerName": AppState().chatDetails!.response!.userName, + "callerEmail": AppState().chatDetails!.response!.email, + "callerTitle": AppState().chatDetails!.response!.title, + "callerPhone": AppState().chatDetails!.response!.phone, + "receiverID": 266642, + "receiverName": "Muhammad Alam", + "receiverEmail": "Muhamad.Alam@cloudsolutions.com.sa", + "receiverTitle": "Muhammad Alam", + "receiverPhone": "123456789", + "title": "Muhammad Alam", + "callType": callType == "VIDEO" ? "Video" : "Audio", + }; + CallDataModel res = CallDataModel.fromJson(json); + return res; + } + + void registerCallListners() { + if (kDebugMode) { + print("=================== Call Listeners Registered ======================="); + } + chatHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync); + chatHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync); + chatHubConnection!.on("OnOfferAsync", onOfferAsync); + chatHubConnection!.on("OnAnswerOffer", onAnswerOffer); + chatHubConnection!.on("OnHangUpAsync", onHangUpAsync); + // chatHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync); + // chatHubConnection!.on("OnIncomingCallAsync", OnIncomingCallAsync); + } + + // Audio Constraints + Map audioConstraints = { + "sampleRate": 8000, + "sampleSize": 16, + "channelCount": 2, + "echoCancellation": true, + "audio": true, + }; + + Future init() async { + _pc = await creatOfferWithCon(); + Future.delayed(const Duration(seconds: 2), () { + connectIncomingCall(); + }); + } + + ///////////////////////////////////////////////OutGoing Call//////////////////////////////////////////////////// + + Future initLocalCamera({callData, bool isIncomingCall = false}) async { + isCallEnded = false; + outGoingCallData = callData; + await initStreams(); + await startCall(); + _pc = await creatOfferWithCon(); + connectOutgoing(); + notifyListeners(); + } + + void connectOutgoing() { + isOutGoingCall = true; + // notifyListeners(); + } + + Future startCall() async { + // chatProvModel.isTextMsg = true; + // chatProvModel.isAttachmentMsg = false; + // chatProvModel.isVoiceMsg = false; + // chatProvModel.isReplyMsg = false; + // chatProvModel.isCall = true; + // chatProvModel.message.text = "Start $callType call ${outGoingCallData.receiverName.toString().replaceAll(".", " ")}"; + // chatProvModel.sendChatMessage( + // context, + // targetUserId: outGoingCallData.receiverId, + // userStatus: 1, + // userEmail: outGoingCallData.receiverEmail, + // targetUserName: outGoingCallData.receiverName, + // ); + await invoke( + invokeMethod: "CallUserAsync", + currentUserID: outGoingCallData!.callerId!, + targetUserID: outGoingCallData!.receiverId!, + ); + await invoke(invokeMethod: "UpdateUserStatusAsync", currentUserID: outGoingCallData!.callerId!, targetUserID: outGoingCallData!.receiverId!, userStatus: 4); + } + + // OutGoing Listeners + void onCallAcceptedAsync(List? params) async { + dynamic items = params!.toList(); + RTCSessionDescription description = await _createOffer(); + await _pc!.setLocalDescription(description); + dynamic payload = {"target": items[0]["id"], "caller": outGoingCallData!.callerId, "sdp": description.toMap()}; + invoke(invokeMethod: "OfferAsync", currentUserID: outGoingCallData!.callerId!, targetUserID: items[0]["id"], data: jsonEncode(payload)); + } + + Future onIceCandidateAsync(List? params) async { + dynamic items = params!.toList(); + if (isIncomingCall) { + RemoteIceCandidatePayLoad data = RemoteIceCandidatePayLoad.fromJson(jsonDecode(items.first.toString())); + if (_pc != null) { + await _pc!.addCandidate(RTCIceCandidate(data.candidate!.candidate, data.candidate!.sdpMid, data.candidate!.sdpMLineIndex)); + } + } else { + if (kDebugMode) { + print("res: " + items.toString()); + } + RemoteIceCandidatePayLoad data = RemoteIceCandidatePayLoad.fromJson(jsonDecode(items.first.toString())); + if (_pc != null) { + await _pc!.addCandidate(RTCIceCandidate(data.candidate!.candidate, data.candidate!.sdpMid, data.candidate!.sdpMLineIndex)); + if (!isCallStarted) { + isCallStarted = true; + notifyListeners(); + if (isCallStarted) { + isIncomingCallLoader = false; + isOutGoingCall = true; + if (Platform.isIOS) { + Future.delayed(Duration(seconds: 2), () { + Navigator.pushReplacement( + locator().navigatorKey.currentContext!, + MaterialPageRoute( + builder: (BuildContext context) => StartCallPage(), + )); + }); + } else { + Navigator.pushReplacement( + locator().navigatorKey.currentContext!, + MaterialPageRoute( + builder: (BuildContext context) => StartCallPage(), + )); + } + } + } + } + notifyListeners(); + } + } + + Future onOfferAsync(List? params) async { + dynamic items = params!.toList(); + var data = jsonDecode(items.toString()); + if (isIncomingCall) { + _pc!.setRemoteDescription(RTCSessionDescription(data[0]["sdp"]["sdp"], data[0]["sdp"]["type"])); + RTCSessionDescription description = await _createAnswer(); + await _pc!.setLocalDescription(description); + dynamic payload = {"target": data[0]["caller"], "caller": AppState().chatDetails!.response!.id!, "sdp": description.toMap()}; + invoke(invokeMethod: "AnswerOfferAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData!.targetUserId!, data: jsonEncode(payload)); + } + // else { + // RTCSessionDescription description = await _createAnswer(); + // await _pc.setLocalDescription(description); + // var payload = {"target": items[0]["id"], "caller": outGoingCallData.callerId, "sdp": description.toMap()}; + // invoke(invokeMethod: "AnswerOffer", currentUserID: outGoingCallData.callerId!, targetUserID: items[0]["id"], data: jsonEncode(payload)); + // } + notifyListeners(); + } + + //////////////////////////// OutGoing Call End /////////////////////////////////////// + + Future endCall({bool? isUserOnline}) async { + if (isIncomingCall) { + print("-----------------------Endeddddd By Me---------------------------"); + if (chatHubConnection!.state == HubConnectionState.Connected) { + await invoke(invokeMethod: "HangUpAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData!.targetUserId!, userStatus: 0); + await invoke(invokeMethod: "UpdateUserStatusAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData!.targetUserId!, userStatus: 1); + } + isCallStarted = false; + isVideoCall = false; + isCamOff = false; + isMicOff = false; + isLoudSpeaker = false; + isIncomingCall = false; + isOutGoingCall = false; + isAudioCall = false; + + if (isCallConnected) { + if (_pc!.connectionState == RTCPeerConnectionState.RTCPeerConnectionStateConnected) { + if (kDebugMode) { + print("------------------ PC Stopped ----------------------------"); + } + _pc!.close(); + _pc!.dispose(); + } + } + if (remoteRenderer != null) { + remoteRenderer!.dispose(); + remoteRenderer = null; + } + if (localVideoRenderer != null) { + localVideoRenderer!.dispose(); + localVideoRenderer = null; + } + + if (_localStream != null) { + _localStream!.dispose(); + _localStream = null; + } + if (chatHubConnection != null && !isUserOnline!) { + chatHubConnection!.stop(); + } + await FlutterCallkitIncoming.endAllCalls(); + return true; + } else { + if (isOutGoingCall) { + await invoke(invokeMethod: "HangUpAsync", currentUserID: outGoingCallData!.callerId!, targetUserID: outGoingCallData!.receiverId!, userStatus: 1); + await invoke(invokeMethod: "UpdateUserStatusAsync", currentUserID: outGoingCallData!.callerId!, targetUserID: outGoingCallData!.receiverId!, userStatus: 1); + } else if (isIncomingCall) { + await invoke(invokeMethod: "UpdateUserStatusAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData!.targetUserId!, userStatus: 1); + } + isCallStarted = false; + isVideoCall = false; + isCamOff = false; + isMicOff = false; + isLoudSpeaker = false; + if (isCallConnected) { + if (_pc!.connectionState == RTCPeerConnectionState.RTCPeerConnectionStateConnected) { + _pc!.close(); + _pc!.dispose(); + } + } + if (remoteRenderer != null) { + remoteRenderer!.dispose(); + remoteRenderer = null; + } + if (localVideoRenderer != null) { + localVideoRenderer!.dispose(); + localVideoRenderer = null; + } + + if (_localStream != null) { + _localStream!.dispose(); + _localStream = null; + } + isOutGoingCall = false; + isIncomingCall = false; + isAudioCall = false; + return true; + } + } + + // Incoming Listeners + + void onAnswerOffer(List? payload) async { + // if (isIncomingCall) { + // // print("--------------------- On Answer Offer Async ---------------------------------------"); + // //await invoke(invokeMethod: "InvokeMobile", currentUserID: AppState().getchatUserDetails!.response!.id!, targetUserID: incomingCallData.targetUserId!, debugData: {"On Answer Offer Async"}); + // } else { + var items = payload!.toList(); + if (kDebugMode) { + print("res: " + items.toString()); + } + CallSessionPayLoad data = CallSessionPayLoad.fromJson(jsonDecode(items.first.toString())); + RTCSessionDescription description = RTCSessionDescription(data.sdp!.sdp, 'answer'); + _pc!.setRemoteDescription(description); + // } + } + + void onHangUpAsync(List? params) { + print("--------------------- onHangUp ASYNC ---------------------------------"); + + dynamic items = params!.toList(); + // if (kDebugMode) { + // logger.i("res: " + items.toString()); + // } + if (items[0]["id"] != AppState().chatDetails!.response!.id!) { + if (kDebugMode) { + print("Call Ended By Other User"); + } + if (isIncomingCall) { + endCall(isUserOnline: isUserOnline).then((bool value) { + if (isCallConnected && isUserOnline) { + isCallConnected = false; + if (!AppState().isLogged) { + Navigator.of(locator().navigatorKey.currentContext!).pop(); + } else { + Navigator.of(locator().navigatorKey.currentContext!).popUntil(ModalRoute.withName(HOME)); + } + } else { + Navigator.of(locator().navigatorKey.currentContext!).pop(); + } + }); + } else { + if (isOutGoingCall) { + endCall(isUserOnline: isUserOnline).then((bool value) { + if (isCallConnected && isUserOnline) { + isCallConnected = false; + Navigator.of(locator().navigatorKey.currentContext!).popUntil(ModalRoute.withName(HOME)); + } else { + Navigator.of(locator().navigatorKey.currentContext!).pop(); + } + }); + } + } + } else { + if (kDebugMode) { + print("Call Ended By Me"); + } + if (isOutGoingCall) { + if (isCallConnected && isUserOnline) { + isCallConnected = false; + Navigator.of(locator().navigatorKey.currentContext!).popUntil(ModalRoute.withName(HOME)); + } else { + Navigator.of(locator().navigatorKey.currentContext!).pop(); + } + } + } + // endCall(isUserOnline: isUserOnline).then((bool value) { + // if (isCallConnected && isUserOnline) { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.chatDetailed)); + // isCallConnected = false; + // } + // if (items[0]["id"] != AppState().chatDetails!!.response!.id && !AppState().isBackgroundCall) { + // if (kDebugMode) { + // print("Popped Due to Another User"); + // } + // if (AppState().isBackgroundCall) { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).pop(); + // // Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.login)); + // } else { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).popUntil(ModalRoute.withName(AppRoutes.chat)); + // } + // } + // if (AppState().isBackgroundCall) { + // Navigator.of(AppRoutes.navigatorKey.currentContext!).pop(); + // } + // + notifyListeners(); + isCallEnded = true; + // }); + } + + // Future OnIncomingCallAsync(List? params) async { + // print("--------------------- On Incoming Call ---------------------------------------"); + // dynamic items = params!.toList(); + // logger.d(items); + // // Map json = { + // // "callerID": items[0]["id"], + // // "callerName": items[0]["userName"], + // // "callerEmail": items[0]["email"], + // // "callerTitle": items[0]["title"], + // // "callerPhone": null, + // // "receiverID": AppState().chatDetails!!.response!.id, + // // "receiverName": AppState().chatDetails!!.response!.userName, + // // "receiverEmail": AppState().chatDetails!!.response!.email, + // // "receiverTitle": AppState().chatDetails!!.response!.title, + // // "receiverPhone": AppState().chatDetails!!.response!.phone, + // // "title": AppState().chatDetails!!.response!.userName!.replaceAll(".", " "), + // // "callType": items[1] ? "Video" : "Audio", + // // }; + // // CallDataModel callData = CallDataModel.fromJson(json); + // // ChatVoipCall().showCallkitIncoming(uuid: const Uuid().v4(), isOnline: true, incomingCallData: callData); + // // + // // if (!isOnIncomingCallPage) { + // // Map json = { + // // "callerID": items[0]["id"], + // // "callerName": items[0]["userName"], + // // "callerEmail": items[0]["email"], + // // "callerTitle": items[0]["title"], + // // "callerPhone": null, + // // "receiverID": AppState().chatDetails!!.response!.id, + // // "receiverName": AppState().chatDetails!!.response!.userName, + // // "receiverEmail": AppState().chatDetails!!.response!.email, + // // "receiverTitle": AppState().chatDetails!!.response!.title, + // // "receiverPhone": AppState().chatDetails!!.response!.phone, + // // "title": AppState().chatDetails!!.response!.userName!.replaceAll(".", " "), + // // "callType": items[1] ? "Video" : "Audio", + // // }; + // // CallDataModel callData = CallDataModel.fromJson(json); + // // await Navigator.push( + // // providerContext, + // // MaterialPageRoute( + // // builder: (BuildContext context) => IncomingCall( + // // isVideoCall: items[1] ? true : false, + // // outGoingCallData: callData, + // // ), + // // ), + // // ); + // // isOnIncomingCallPage = true; + // // } + // } + + void onCallDeclinedAsync(List params) { + print("================= On Declained ========================"); + print(params); + // endCall().then((bool value) { + // if (value) { + // isCallEnded = true; + // notifyListeners(); + // } + // }); + // if (params != null) { + // endCall(isUserOnline: isUserOnline).then((bool value) { + // if (isCallConnected) { + // // Navigator.of(AppRoutes.navigatorKey.currentContext!).pop(); + // isCallConnected = false; + // } + // isCallEnded = true; + // }); + // } + } + + //// Invoke Methods + + Future invoke({required String invokeMethod, required int currentUserID, required int targetUserID, var data, int userStatus = 1, var debugData}) async { + List args = []; + if (invokeMethod == "CallUserAsync") { + args = [currentUserID, targetUserID, isVideoCall]; + } else if (invokeMethod == "answerCallAsync") { + args = [currentUserID, targetUserID]; + } else if (invokeMethod == "IceCandidateAsync") { + args = [targetUserID, data]; + } else if (invokeMethod == "OfferAsync") { + args = [targetUserID, data]; + } else if (invokeMethod == "AnswerOfferAsync") { + args = [targetUserID, data]; + // json In Data + } else if (invokeMethod == "UpdateUserStatusAsync") { + args = [currentUserID, userStatus]; + } else if (invokeMethod == "HangUpAsync") { + args = [currentUserID, targetUserID]; + } else if (invokeMethod == "InvokeMobile") { + args = [debugData]; + } + try { + await chatHubConnection!.invoke("$invokeMethod", args: args); + } catch (e) { + print(e); + } + } + + void stopListeners() async { + chatHubConnection!.off('OnCallDeclinedAsync'); + chatHubConnection!.off('OnCallAcceptedAsync'); + chatHubConnection!.off('OnIceCandidateAsync'); + chatHubConnection!.off('OnAnswerOffer'); + } + + void playRingtone() async { + player.stop(); + await player.setVolume(1.0); + String audioAsset = ""; + if (Platform.isAndroid) { + audioAsset = "assets/audio/ring_60Sec.mp3"; + } else { + audioAsset = "assets/audio/ring_30Sec.caf"; + } + try { + await player.setAsset(audioAsset); + await player.load(); + player.play(); + } catch (e) { + print("Error: $e"); + } + } + + //////////////////// Web RTC Offers & Connections //////////////////////// + + Future creatOfferWithCon() async { + Map configuration = { + "sdpSemantics": "plan-b", + 'iceServers': [ + { + 'urls': 'stun:15.185.116.59:3478', + }, + { + 'urls': 'turn:15.185.116.59:3479', + 'username': 'admin', + 'credential': 'admin', + }, + ] + }; + Map offerSdpConstraints = { + 'mandatory': { + 'OfferToReceiveAudio': true, + 'OfferToReceiveVideo': true, + }, + 'optional': [] + }; + RTCPeerConnection pc = await createPeerConnection(configuration, offerSdpConstraints); + // await pc.addStream(_localStream!); + //Changed By Aamir + _localStream?.getTracks().forEach((track) { + pc.addTrack(track, _localStream!); + }); + pc.onConnectionState = (RTCPeerConnectionState state) {}; + pc.onAddStream = (MediaStream stream) { + remoteRenderer!.srcObject = stream; + notifyListeners(); + }; + pc.onIceCandidate = (RTCIceCandidate e) async { + if (isIncomingCall) { + if (e.candidate != null) { + var payload = {"target": incomingCallData!.targetUserId, "candidate": e.toMap()}; + invoke(invokeMethod: "IceCandidateAsync", currentUserID: AppState().chatDetails!.response!.id!, targetUserID: incomingCallData!.targetUserId!, data: jsonEncode(payload)); + notifyListeners(); + } + } else { + if (e.candidate != null) { + var payload = {"target": outGoingCallData!.callerId, "candidate": e.toMap()}; + invoke(invokeMethod: "IceCandidateAsync", currentUserID: outGoingCallData!.callerId!, targetUserID: outGoingCallData!.receiverId!, data: jsonEncode(payload)); + } + } + }; + // pc!.onTrack = (RTCTrackEvent event) async { + // + // String streamId = const Uuid().toString(); + // MediaStream remoteStream = await createLocalMediaStream(streamId); + // event.streams[0].getTracks().forEach((MediaStreamTrack element) { + // logger.i("Stream Track: " + element.id.toString()); + // // remoteRenderer.srcObject = element; + // remoteStream.addTrack(element); + // }); + // }; + pc.onSignalingState = (RTCSignalingState state) { + print("signaling state: " + state.name); + // invoke( + // invokeMethod: "InvokeMobile", + // currentUserID: AppState().getchatUserDetails!.response!.id!, + // targetUserID: incomingCallData.targetUserId!, + // debugData: {"location": "Signaling", "parms": state.name}); + }; + pc.onIceGatheringState = (RTCIceGatheringState state) { + print("rtc ice gathering state: " + state.name); + }; + pc.onIceConnectionState = (RTCIceConnectionState state) { + if (RTCIceConnectionState.RTCIceConnectionStateFailed == state || + RTCIceConnectionState.RTCIceConnectionStateDisconnected == state || + RTCIceConnectionState.RTCIceConnectionStateClosed == state) { + print("Ice Connection State:" + state.name); + + // endCall().then((value) { + // notifyListeners(); + // }); + } + }; + // pc!.onRenegotiationNeeded = _onRenegotiate; + return pc; + } + + // void _onRenegotiate() async { + // try { + // print('onRenegotiationNeeded start'); + // // makingOffer = true; + // await _pc.setLocalDescription(await _pc.createOffer(videoConstraints)); + // print('onRenegotiationNeeded state after setLocalDescription: ' + _pc.signalingState.toString()); + // // send offer via callManager + // var localDesc = await _pc.getLocalDescription(); + // // callManager.sendCallMessage(MsgType.rtc_offer, RtcOfferAnswer(localDesc.sdp, localDesc.type)); + // print('onRenegotiationNeeded; offer sent'); + // } catch (e) { + // print("onRenegotiationNeeded error: " + e.toString()); + // } finally { + // // makingOffer = false; + // print('onRenegotiationNeeded done'); + // } + // } + + Future _createOffer() async { + RTCSessionDescription description = await _pc!.createOffer(); + // _offer = true; + return description; + } + + Future _createAnswer() async { + RTCSessionDescription description = await _pc!.createAnswer(); + // _offer = false; + return description; + } + + //////////////////// Web RTC End Offers //////////////////// + + //////////////////// CallPage Buttons ////////////////////// + + void micOff() { + isMicOff = !isMicOff; + _localStream!.getAudioTracks().forEach((track) { + track.enabled = !track.enabled; + }); + notifyListeners(); + } + + void camOff() { + isCamOff = !isCamOff; + _localStream!.getVideoTracks().forEach((track) { + track.enabled = !track.enabled; + }); + // if (isCamOff) { + // isVideoCall = false; + // } else { + // isVideoCall = true; + // } + notifyListeners(); + } + + void loudOn() { + isLoudSpeaker = !isLoudSpeaker; + remoteRenderer!.srcObject?.getAudioTracks().forEach((track) { + if (isLoudSpeaker) { + track.enableSpeakerphone(true); + } else { + track.enableSpeakerphone(false); + } + }); + notifyListeners(); + } + + void switchCamera() { + isFrontCamera = !isFrontCamera; + Helper.switchCamera(_localStream!.getVideoTracks()[0]); + notifyListeners(); + } + + ///////////////// Incoming Call /////////////////////////////// + + Future initStreams() async { + List devices = await navigator.mediaDevices.enumerateDevices(); + remoteRenderer = RTCVideoRenderer(); + localVideoRenderer ??= RTCVideoRenderer(); + await localVideoRenderer!.initialize(); + try { + _localStream = await navigator.mediaDevices.getUserMedia({ + 'audio': true, + 'video': + //isVideoCall? + { + 'mandatory': { + 'minWidth': '640', // Provide your own width, height and frame rate here + 'minHeight': '480', + 'minFrameRate': '30', + }, + 'facingMode': 'user', + 'optional': [], + "audio": true, + } + // : false + }); + localVideoRenderer!.srcObject = _localStream; + localVideoRenderer!.value = (const RTCVideoValue( + width: 200, + height: 200, + renderVideo: true, + )); + print("Working localStream"); + } catch (e) { + print("Failed to get user media: $e"); + } + + // localVideoRenderer.srcObject = _localStream; + await remoteRenderer!.initialize(); + notifyListeners(); + } + + Future startIncomingCallViaKit({bool isVCall = true, var inCallData}) async { + //AppSharedPrefs().setStringFromPrefs("isIncomingCall", "false"); + if (isVCall) { + isVideoCall = isVCall; + } else { + isAudioCall = true; + } + await initStreams(); + isIncomingCall = true; + incomingCallData = SingleUserChatModel.fromJson(inCallData); + loudOn(); + // notifyListeners(); + } + + void connectIncomingCall() { + invoke(invokeMethod: "answerCallAsync", currentUserID: AppState().getchatUserDetails.response!.id!, targetUserID: incomingCallData!.targetUserId!); + isIncomingCallLoader = false; + isIncomingCall = true; + // isVideoCall = true; + + notifyListeners(); + } + +// void startRecording() async { +// print("=-=-=-=-=-=-= Call Recoding Started -=-=-=-=-=-=-=-=-==-="); +// if (_localStream == null) throw Exception('Stream is not initialized'); +// if (Platform.isIOS) { +// print('Recording is not available on iOS'); +// return; +// } +// Directory appDirectory = await getApplicationDocumentsDirectory(); +// String dirPath = '${appDirectory.path}/webrtc_sample'; +// if (!await Directory(dirPath).exists()) { +// await Directory(dirPath).create(); +// await File('$dirPath/.nomedia').create(); +// } +// if (appDirectory == null) throw Exception('Can\'t find storagePath'); +// String filePath = dirPath + '/mobile.mp4'; +// mobileRecoder = MediaRecorder(); +// notifyListeners(); +// MediaStreamTrack videoTrack = _localStream!.getVideoTracks().firstWhere((track) => track.kind == 'video'); +// await mobileRecoder!.start(filePath, videoTrack: videoTrack); +// +// Future.delayed(Duration(minutes: 1), () { +// stopRecording(); +// }); +// } +// +// void stopRecording() async { +// print("=-=-=-=-=-=-= Call Recoding Stopped -=-=-=-=-=-=-=-=-==-="); +// await mobileRecoder!.stop(); +// mobileRecoder = null; +// notifyListeners(); +// } +} + +class ChatService { + Future getUserCallToken({required String userid}) async { + UserAutoLoginModel userLoginResponse = UserAutoLoginModel(); + var headers = {'Content-Type': 'application/json'}; + var request = http.Request('POST', Uri.parse("${ApiConsts.chatLoginTokenUrl}externaluserlogin")); + request.body = json.encode({"employeeNumber": "341682", "password": "FxIu26rWIKoF8n6mpbOmAjDLphzFGmpG"}); + request.headers.addAll(headers); + http.StreamedResponse response = await request.send(); + if (response.statusCode == 200) { + userLoginResponse = userAutoLoginModelFromJson(await response.stream.bytesToString()); + } else { + print(response.reasonPhrase); + } + + return userLoginResponse; + } +} diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index b12aacbb..00000000 --- a/pubspec.lock +++ /dev/null @@ -1,1529 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - url: "https://pub.dartlang.org" - source: hosted - version: "22.0.0" - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.12" - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.2" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "3.3.5" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.1" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.9.0" - autocomplete_textfield: - dependency: "direct main" - description: - name: autocomplete_textfield - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - badges: - dependency: "direct main" - description: - name: badges - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" - barcode_scan2: - dependency: "direct main" - description: - name: barcode_scan2 - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - bazel_worker: - dependency: transitive - description: - name: bazel_worker - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.3" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.6" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.10" - build_modules: - dependency: transitive - description: - name: build_modules - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.5" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.4" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.11.5" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.10" - build_web_compilers: - dependency: "direct dev" - description: - name: build_web_compilers - url: "https://pub.dartlang.org" - source: hosted - version: "2.16.5" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "5.1.1" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "8.4.2" - cached_network_image: - dependency: "direct main" - description: - name: cached_network_image - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.3" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.1" - charts_common: - dependency: transitive - description: - name: charts_common - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0" - charts_flutter: - dependency: "direct main" - description: - name: charts_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - chewie: - dependency: transitive - description: - name: chewie - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.6" - chewie_audio: - dependency: transitive - description: - name: chewie_audio - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.7.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.16.0" - connectivity: - dependency: "direct main" - description: - name: connectivity - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.6" - connectivity_for_web: - dependency: transitive - description: - name: connectivity_for_web - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.0+1" - connectivity_macos: - dependency: transitive - description: - name: connectivity_macos - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.1+2" - connectivity_platform_interface: - dependency: transitive - description: - name: connectivity_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.2" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.17.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.14" - date_time_picker: - dependency: "direct main" - description: - name: date_time_picker - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - device_info: - dependency: "direct main" - description: - name: device_info - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" - device_info_platform_interface: - dependency: transitive - description: - name: device_info_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - dropdown_search: - dependency: "direct main" - description: - name: dropdown_search - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - equatable: - dependency: transitive - description: - name: equatable - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" - eva_icons_flutter: - dependency: "direct main" - description: - name: eva_icons_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - expandable: - dependency: "direct main" - description: - name: expandable - url: "https://pub.dartlang.org" - source: hosted - version: "5.0.1" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - ffi: - dependency: transitive - description: - name: ffi - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.1" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.4" - file_picker: - dependency: "direct main" - description: - name: file_picker - url: "https://pub.dartlang.org" - source: hosted - version: "4.6.1" - firebase_analytics: - dependency: "direct main" - description: - name: firebase_analytics - url: "https://pub.dartlang.org" - source: hosted - version: "10.1.0" - firebase_analytics_platform_interface: - dependency: transitive - description: - name: firebase_analytics_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "3.3.17" - firebase_analytics_web: - dependency: transitive - description: - name: firebase_analytics_web - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.1+8" - firebase_core: - dependency: transitive - description: - name: firebase_core - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.1" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "4.5.2" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - url: "https://pub.dartlang.org" - source: hosted - version: "14.2.1" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.10" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.11" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - fl_chart: - dependency: "direct main" - description: - name: fl_chart - url: "https://pub.dartlang.org" - source: hosted - version: "0.40.6" - flex_color_picker: - dependency: transitive - description: - name: flex_color_picker - url: "https://pub.dartlang.org" - source: hosted - version: "2.6.1" - flex_seed_scheme: - dependency: transitive - description: - name: flex_seed_scheme - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_blurhash: - dependency: transitive - description: - name: flutter_blurhash - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.0" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - url: "https://pub.dartlang.org" - source: hosted - version: "3.3.0" - flutter_colorpicker: - dependency: "direct main" - description: - name: flutter_colorpicker - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - flutter_datetime_picker: - dependency: "direct main" - description: - name: flutter_datetime_picker - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.1" - flutter_device_type: - dependency: "direct main" - description: - name: flutter_device_type - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.0" - flutter_gifimage: - dependency: "direct main" - description: - name: flutter_gifimage - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - flutter_html: - dependency: "direct main" - description: - name: flutter_html - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.1" - flutter_inappwebview: - dependency: transitive - description: - name: flutter_inappwebview - url: "https://pub.dartlang.org" - source: hosted - version: "5.7.2+3" - flutter_keyboard_visibility: - dependency: transitive - description: - name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" - source: hosted - version: "5.4.0" - flutter_keyboard_visibility_linux: - dependency: transitive - description: - name: flutter_keyboard_visibility_linux - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_macos: - dependency: transitive - description: - name: flutter_keyboard_visibility_macos - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_platform_interface: - dependency: transitive - description: - name: flutter_keyboard_visibility_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_web: - dependency: transitive - description: - name: flutter_keyboard_visibility_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_windows: - dependency: transitive - description: - name: flutter_keyboard_visibility_windows - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_layout_grid: - dependency: transitive - description: - name: flutter_layout_grid - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - flutter_localizations: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_math_fork: - dependency: transitive - description: - name: flutter_math_fork - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" - flutter_page_indicator: - dependency: transitive - description: - name: flutter_page_indicator - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.3" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.7" - flutter_staggered_grid_view: - dependency: "direct main" - description: - name: flutter_staggered_grid_view - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.1" - flutter_svg: - dependency: transitive - description: - name: flutter_svg - url: "https://pub.dartlang.org" - source: hosted - version: "0.23.0+1" - flutter_swiper: - dependency: "direct main" - description: - name: flutter_swiper - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - fluttertoast: - dependency: "direct main" - description: - name: fluttertoast - url: "https://pub.dartlang.org" - source: hosted - version: "8.1.2" - font_awesome_flutter: - dependency: "direct main" - description: - name: font_awesome_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "10.3.0" - get_it: - dependency: "direct main" - description: - name: get_it - url: "https://pub.dartlang.org" - source: hosted - version: "7.2.0" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - hexcolor: - dependency: "direct main" - description: - name: hexcolor - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - hijri: - dependency: transitive - description: - name: hijri - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" - hijri_picker: - dependency: "direct main" - description: - name: hijri_picker - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" - html: - dependency: "direct main" - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.1" - html_editor_enhanced: - dependency: "direct main" - description: - name: html_editor_enhanced - url: "https://pub.dartlang.org" - source: hosted - version: "2.5.0" - http: - dependency: "direct main" - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.5" - http_interceptor: - dependency: "direct main" - description: - name: http_interceptor - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.2" - infinite_listview: - dependency: transitive - description: - name: infinite_listview - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - intl: - dependency: "direct main" - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.17.0" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.4" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "4.7.0" - local_auth: - dependency: "direct main" - description: - name: local_auth - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - local_auth_android: - dependency: transitive - description: - name: local_auth_android - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.17" - local_auth_ios: - dependency: transitive - description: - name: local_auth_ios - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.12" - local_auth_platform_interface: - dependency: transitive - description: - name: local_auth_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.6" - local_auth_windows: - dependency: transitive - description: - name: local_auth_windows - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - maps_launcher: - dependency: "direct main" - description: - name: maps_launcher - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.12" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.5" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - nested: - dependency: transitive - description: - name: nested - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - numberpicker: - dependency: transitive - description: - name: numberpicker - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - numerus: - dependency: transitive - description: - name: numerus - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - octo_image: - dependency: transitive - description: - name: octo_image - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.2" - path_drawing: - dependency: transitive - description: - name: path_drawing - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.1+1" - path_parsing: - dependency: transitive - description: - name: path_parsing - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.1" - path_provider: - dependency: transitive - description: - name: path_provider - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.11" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.22" - path_provider_ios: - dependency: transitive - description: - name: path_provider_ios - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.11" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.7" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.7" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.11.1" - percent_indicator: - dependency: "direct main" - description: - name: percent_indicator - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.2" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - url: "https://pub.dartlang.org" - source: hosted - version: "10.2.0" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - url: "https://pub.dartlang.org" - source: hosted - version: "10.2.0" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - url: "https://pub.dartlang.org" - source: hosted - version: "9.0.7" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "3.9.0" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.2" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "5.1.0" - platform: - dependency: transitive - description: - name: platform - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - pointer_interceptor: - dependency: transitive - description: - name: pointer_interceptor - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.3+3" - pointycastle: - dependency: transitive - description: - name: pointycastle - url: "https://pub.dartlang.org" - source: hosted - version: "3.6.2" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.1" - process: - dependency: transitive - description: - name: process - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.4" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - provider: - dependency: "direct main" - description: - name: provider - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.5" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.8" - quiver: - dependency: "direct main" - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.1" - rxdart: - dependency: transitive - description: - name: rxdart - url: "https://pub.dartlang.org" - source: hosted - version: "0.27.7" - scratch_space: - dependency: transitive - description: - name: scratch_space - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4+3" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.15" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.14" - shared_preferences_ios: - dependency: transitive - description: - name: shared_preferences_ios - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.4+1" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.11" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.0" - speech_to_text: - dependency: "direct main" - description: - name: speech_to_text - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.1" - speech_to_text_macos: - dependency: transitive - description: - name: speech_to_text_macos - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - speech_to_text_platform_interface: - dependency: transitive - description: - name: speech_to_text_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - sqflite: - dependency: transitive - description: - name: sqflite - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.2" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0+2" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - sticky_headers: - dependency: "direct main" - description: - name: sticky_headers - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0+2" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - synchronized: - dependency: transitive - description: - name: synchronized - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0+3" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.12" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+3" - transformer_page_view: - dependency: transitive - description: - name: transformer_page_view - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.6" - tuple: - dependency: transitive - description: - name: tuple - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.7" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.22" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.17" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.13" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" - video_player: - dependency: transitive - description: - name: video_player - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.10" - video_player_android: - dependency: transitive - description: - name: video_player_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.10" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.8" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.1" - video_player_web: - dependency: transitive - description: - name: video_player_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.13" - visibility_detector: - dependency: transitive - description: - name: visibility_detector - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - wakelock: - dependency: transitive - description: - name: wakelock - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.2" - wakelock_macos: - dependency: transitive - description: - name: wakelock_macos - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.0" - wakelock_platform_interface: - dependency: transitive - description: - name: wakelock_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - wakelock_web: - dependency: transitive - description: - name: wakelock_web - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.0" - wakelock_windows: - dependency: transitive - description: - name: wakelock_windows - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - webview_flutter: - dependency: transitive - description: - name: webview_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.0" - webview_flutter_android: - dependency: transitive - description: - name: webview_flutter_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.10.4" - webview_flutter_platform_interface: - dependency: transitive - description: - name: webview_flutter_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.5" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - url: "https://pub.dartlang.org" - source: hosted - version: "2.9.5" - win32: - dependency: transitive - description: - name: win32 - url: "https://pub.dartlang.org" - source: hosted - version: "2.6.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0+2" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "5.4.1" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.1" -sdks: - dart: ">=2.18.1 <3.0.0" - flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index e3e4e844..b33a978e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,7 +76,7 @@ dependencies: # Firebase firebase_messaging: ^14.7.5 - firebase_analytics : ^10.7.1 + firebase_analytics: ^10.7.1 #GIF image # flutter_gifimage: ^1.0.1 @@ -131,6 +131,17 @@ dependencies: flutter_math_fork: ^0.7.2 + #Chat + signalr_netcore: ^1.3.6 + logging: ^1.0.1 + swipe_to: ^1.0.5 + flutter_webrtc: ^0.9.48+hotfix.1 + draggable_widget: ^2.0.0 + flutter_callkit_incoming: ^2.0.0+1 + camera: ^0.10.5+9 + just_audio: ^0.9.36 + + dependency_overrides: flutter_localizations: