diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 57c33126..ffb5fb69 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -22,7 +22,6 @@ jobs: java-version: '12.x' - uses: subosito/flutter-action@v1 with: - flutter-version: '3.7.3' channel: 'stable' - run: flutter packages get - run: dart format lib/ test/ --set-exit-if-changed diff --git a/example/lib/main.dart b/example/lib/main.dart index f21e9260..e1c9b8e5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:sip_ua/sip_ua.dart'; - import 'src/about.dart'; import 'src/callscreen.dart'; import 'src/dialpad.dart'; @@ -57,6 +56,18 @@ class MyApp extends StatelessWidget { theme: ThemeData( primarySwatch: Colors.blue, fontFamily: 'Roboto', + inputDecorationTheme: InputDecorationTheme( + hintStyle: TextStyle(color: Colors.grey), + contentPadding: EdgeInsets.all(10.0), + border: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.black12)), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.all(16), + textStyle: TextStyle(fontSize: 18), + ), + ), ), initialRoute: '/', onGenerateRoute: _onGenerateRoute, diff --git a/example/lib/src/about.dart b/example/lib/src/about.dart index 7109cdf8..f4eacedc 100644 --- a/example/lib/src/about.dart +++ b/example/lib/src/about.dart @@ -4,21 +4,21 @@ class AboutWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("About"), - ), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(18.0), - child: Center( - child: Column( - children: [ - Text( - 'GitHub:\nhttps://github.com/cloudwebrtc/dart-sip-ua.git') - ], - ), + appBar: AppBar( + title: Text("About"), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(18.0), + child: Center( + child: Column( + children: [ + Text('GitHub:\nhttps://github.com/cloudwebrtc/dart-sip-ua.git'), + ], ), ), - )); + ), + ), + ); } } diff --git a/example/lib/src/callscreen.dart b/example/lib/src/callscreen.dart index 0bfa394b..1777efe3 100644 --- a/example/lib/src/callscreen.dart +++ b/example/lib/src/callscreen.dart @@ -10,9 +10,11 @@ import 'widgets/action_button.dart'; class CallScreenWidget extends StatefulWidget { final SIPUAHelper? _helper; final Call? _call; + CallScreenWidget(this._helper, this._call, {Key? key}) : super(key: key); + @override - _MyCallScreenWidget createState() => _MyCallScreenWidget(); + State createState() => _MyCallScreenWidget(); } class _MyCallScreenWidget extends State @@ -27,13 +29,17 @@ class _MyCallScreenWidget extends State bool _showNumPad = false; String _timeLabel = '00:00'; - late Timer _timer; bool _audioMuted = false; bool _videoMuted = false; bool _speakerOn = false; bool _hold = false; + bool _mirror = true; String? _holdOriginator; CallStateEnum _state = CallStateEnum.NONE; + + late String _transferTarget; + late Timer _timer; + SIPUAHelper? get helper => widget._helper; bool get voiceOnly => @@ -237,6 +243,9 @@ class _MyCallScreenWidget extends State void _switchCamera() { if (_localStream != null) { Helper.switchCamera(_localStream!.getVideoTracks()[0]); + setState(() { + _mirror = !_mirror; + }); } } @@ -264,7 +273,6 @@ class _MyCallScreenWidget extends State } } - late String _transferTarget; void _handleTransfer() { showDialog( context: context, @@ -324,7 +332,7 @@ class _MyCallScreenWidget extends State } List _buildNumPad() { - var labels = [ + final labels = [ [ {'1': ''}, {'2': 'abc'}, @@ -364,22 +372,22 @@ class _MyCallScreenWidget extends State } Widget _buildActionButtons() { - var hangupBtn = ActionButton( + final hangupBtn = ActionButton( title: "hangup", onPressed: () => _handleHangup(), icon: Icons.call_end, fillColor: Colors.red, ); - var hangupBtnInactive = ActionButton( + final hangupBtnInactive = ActionButton( title: "hangup", onPressed: () {}, icon: Icons.call_end, fillColor: Colors.grey, ); - var basicActions = []; - var advanceActions = []; + final basicActions = []; + final advanceActions = []; switch (_state) { case CallStateEnum.NONE: @@ -472,67 +480,83 @@ class _MyCallScreenWidget extends State break; } - var actionWidgets = []; + final actionWidgets = []; if (_showNumPad) { actionWidgets.addAll(_buildNumPad()); } else { if (advanceActions.isNotEmpty) { - actionWidgets.add(Padding( + actionWidgets.add( + Padding( padding: const EdgeInsets.all(3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: advanceActions))); + children: advanceActions), + ), + ); } } - actionWidgets.add(Padding( + actionWidgets.add( + Padding( padding: const EdgeInsets.all(3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: basicActions))); + children: basicActions), + ), + ); return Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - children: actionWidgets); + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: actionWidgets, + ); } Widget _buildContent() { - var stackWidgets = []; + final stackWidgets = []; if (!voiceOnly && _remoteStream != null) { - stackWidgets.add(Center( - child: RTCVideoView(_remoteRenderer!), - )); + stackWidgets.add( + Center( + child: RTCVideoView( + _remoteRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), + ), + ); } if (!voiceOnly && _localStream != null) { - stackWidgets.add(Container( - child: AnimatedContainer( - child: RTCVideoView(_localRenderer!), + stackWidgets.add( + AnimatedContainer( + child: RTCVideoView( + _localRenderer!, + mirror: _mirror, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), height: _localVideoHeight, width: _localVideoWidth, alignment: Alignment.topRight, duration: Duration(milliseconds: 300), margin: _localVideoMargin, ), - alignment: Alignment.topRight, - )); + ); } - stackWidgets.addAll([ - Positioned( - top: voiceOnly ? 48 : 6, - left: 0, - right: 0, - child: Center( + stackWidgets.addAll( + [ + Positioned( + top: voiceOnly ? 48 : 6, + left: 0, + right: 0, + child: Center( child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Center( - child: Padding( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Padding( padding: const EdgeInsets.all(6), child: Text( (voiceOnly ? 'VOICE CALL' : 'VIDEO CALL') + @@ -540,23 +564,33 @@ class _MyCallScreenWidget extends State ? ' PAUSED BY ${_holdOriginator!.toUpperCase()}' : ''), style: TextStyle(fontSize: 24, color: Colors.black54), - ))), - Center( - child: Padding( + ), + ), + ), + Center( + child: Padding( padding: const EdgeInsets.all(6), child: Text( '$remoteIdentity', style: TextStyle(fontSize: 18, color: Colors.black54), - ))), - Center( - child: Padding( + ), + ), + ), + Center( + child: Padding( padding: const EdgeInsets.all(6), - child: Text(_timeLabel, - style: TextStyle(fontSize: 14, color: Colors.black54)))) - ], - )), - ), - ]); + child: Text( + _timeLabel, + style: TextStyle(fontSize: 14, color: Colors.black54), + ), + ), + ) + ], + ), + ), + ), + ], + ); return Stack( children: stackWidgets, @@ -566,16 +600,18 @@ class _MyCallScreenWidget extends State @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - title: Text('[$direction] ${EnumHelper.getName(_state)}')), - body: Container( - child: _buildContent(), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: Padding( - padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 24.0), - child: Container(width: 320, child: _buildActionButtons()))); + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text('[$direction] ${EnumHelper.getName(_state)}'), + ), + body: _buildContent(), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: Container( + width: 320, + padding: EdgeInsets.only(bottom: 24.0), + child: _buildActionButtons(), + ), + ); } @override diff --git a/example/lib/src/dialpad.dart b/example/lib/src/dialpad.dart index 3eeecbfe..5a27b879 100644 --- a/example/lib/src/dialpad.dart +++ b/example/lib/src/dialpad.dart @@ -9,9 +9,11 @@ import 'widgets/action_button.dart'; class DialPadWidget extends StatefulWidget { final SIPUAHelper? _helper; + DialPadWidget(this._helper, {Key? key}) : super(key: key); + @override - _MyDialPadWidget createState() => _MyDialPadWidget(); + State createState() => _MyDialPadWidget(); } class _MyDialPadWidget extends State @@ -46,7 +48,7 @@ class _MyDialPadWidget extends State Future _handleCall(BuildContext context, [bool voiceOnly = false]) async { - var dest = _textController?.text; + final dest = _textController?.text; if (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS) { await Permission.microphone.request(); @@ -74,7 +76,14 @@ class _MyDialPadWidget extends State return null; } - final mediaConstraints = {'audio': true, 'video': true}; + final mediaConstraints = { + 'audio': true, + 'video': { + 'width': '1280', + 'height': '720', + 'facingMode': 'user', + } + }; MediaStream mediaStream; @@ -84,9 +93,14 @@ class _MyDialPadWidget extends State mediaConstraints['video'] = false; MediaStream userStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); - mediaStream.addTrack(userStream.getAudioTracks()[0], addToNative: true); + final audioTracks = userStream.getAudioTracks(); + if (audioTracks.isNotEmpty) { + mediaStream.addTrack(audioTracks.first, addToNative: true); + } } else { - mediaConstraints['video'] = !voiceOnly; + if (voiceOnly) { + mediaConstraints['video'] = !voiceOnly; + } mediaStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); } @@ -112,7 +126,7 @@ class _MyDialPadWidget extends State } List _buildNumPad() { - var labels = [ + final labels = [ [ {'1': ''}, {'2': 'abc'}, @@ -153,145 +167,137 @@ class _MyDialPadWidget extends State List _buildDialPad() { return [ + Align( + alignment: AlignmentDirectional.centerStart, + child: Text('Destination URL'), + ), + const SizedBox(height: 8), Container( - width: 360, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 360, - child: TextField( - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 24, color: Colors.black54), - decoration: InputDecoration( - border: InputBorder.none, - ), - controller: _textController, - )), - ])), + width: 500, + child: TextField( + keyboardType: TextInputType.text, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, color: Colors.black54), + maxLines: 3, + decoration: InputDecoration( + border: OutlineInputBorder(), + ), + controller: _textController, + ), + ), Container( - width: 300, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: _buildNumPad())), + width: 500, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildNumPad(), + ), + ), Container( - width: 300, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ActionButton( - icon: Icons.videocam, - onPressed: () => _handleCall(context), - ), - ActionButton( - icon: Icons.dialer_sip, - fillColor: Colors.green, - onPressed: () => _handleCall(context, true), - ), - ActionButton( - icon: Icons.keyboard_arrow_left, - onPressed: () => _handleBackSpace(), - onLongPress: () => _handleBackSpace(true), - ), - ], - ))) + width: 500, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ActionButton( + icon: Icons.videocam, + onPressed: () => _handleCall(context), + ), + ActionButton( + icon: Icons.dialer_sip, + fillColor: Colors.green, + onPressed: () => _handleCall(context, true), + ), + ActionButton( + icon: Icons.keyboard_arrow_left, + onPressed: () => _handleBackSpace(), + onLongPress: () => _handleBackSpace(true), + ), + ], + ), + ), + ), ]; } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Dart SIP UA Demo"), - actions: [ - PopupMenuButton( - onSelected: (String value) { - switch (value) { - case 'account': - Navigator.pushNamed(context, '/register'); - break; - case 'about': - Navigator.pushNamed(context, '/about'); - break; - default: - break; - } - }, - icon: Icon(Icons.menu), - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), - child: Icon( - Icons.account_circle, - color: Colors.black38, - ), - ), - SizedBox( - child: Text('Account'), - width: 64, - ) - ], - ), - value: 'account', + appBar: AppBar( + title: Text("Dart SIP UA Demo"), + actions: [ + PopupMenuButton( + onSelected: (String value) { + switch (value) { + case 'account': + Navigator.pushNamed(context, '/register'); + break; + case 'about': + Navigator.pushNamed(context, '/about'); + break; + default: + break; + } + }, + icon: Icon(Icons.menu), + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + child: Row( + children: [ + Icon( + Icons.account_circle, + color: Colors.black54, + ), + SizedBox(width: 12), + Text('Account'), + ], ), - PopupMenuItem( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon( - Icons.info, - color: Colors.black38, - ), - SizedBox( - child: Text('About'), - width: 64, - ) - ], - ), - value: 'about', - ) - ]), - ], - ), - body: Align( - alignment: Alignment(0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(6.0), - child: Center( - child: Text( - 'Status: ${EnumHelper.getName(helper!.registerState.state)}', - style: TextStyle(fontSize: 14, color: Colors.black54), - )), - ), - Padding( - padding: const EdgeInsets.all(6.0), - child: Center( - child: Text( - 'Received Message: $receivedMsg', - style: TextStyle(fontSize: 14, color: Colors.black54), - )), - ), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: _buildDialPad(), - )), - ]))); + value: 'account', + ), + PopupMenuItem( + child: Row( + children: [ + Icon( + Icons.info, + color: Colors.black54, + ), + SizedBox(width: 12), + Text('About'), + ], + ), + value: 'about', + ) + ]), + ], + ), + body: ListView( + padding: EdgeInsets.symmetric(horizontal: 12), + children: [ + SizedBox(height: 20), + Center( + child: Text( + 'Register Status: ${EnumHelper.getName(helper!.registerState.state)}', + style: TextStyle(fontSize: 18, color: Colors.black54), + ), + ), + SizedBox(height: 12), + Center( + child: Text( + 'Received Message: $receivedMsg', + style: TextStyle(fontSize: 16, color: Colors.black54), + ), + ), + SizedBox(height: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildDialPad(), + ), + ], + ), + ); } @override diff --git a/example/lib/src/register.dart b/example/lib/src/register.dart index 91f5dc5e..62c6de7c 100644 --- a/example/lib/src/register.dart +++ b/example/lib/src/register.dart @@ -4,9 +4,11 @@ import 'package:sip_ua/sip_ua.dart'; class RegisterWidget extends StatefulWidget { final SIPUAHelper? _helper; + RegisterWidget(this._helper, {Key? key}) : super(key: key); + @override - _MyRegisterWidget createState() => _MyRegisterWidget(); + State createState() => _MyRegisterWidget(); } class _MyRegisterWidget extends State @@ -27,7 +29,7 @@ class _MyRegisterWidget extends State SIPUAHelper? get helper => widget._helper; @override - initState() { + void initState() { super.initState(); _registerState = helper!.registerState; helper!.addSipUaHelperListener(this); @@ -35,7 +37,17 @@ class _MyRegisterWidget extends State } @override - deactivate() { + void dispose() { + _passwordController.dispose(); + _wsUriController.dispose(); + _sipUriController.dispose(); + _displayNameController.dispose(); + _authorizationUserController.dispose(); + super.dispose(); + } + + @override + void deactivate() { super.deactivate(); helper!.removeSipUaHelperListener(this); _saveSettings(); @@ -119,167 +131,75 @@ class _MyRegisterWidget extends State @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("SIP Account"), - ), - body: Align( - alignment: Alignment(0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Column( - children: [ - Padding( - padding: - const EdgeInsets.fromLTRB(48.0, 18.0, 48.0, 18.0), - child: Center( - child: Text( - 'Register Status: ${EnumHelper.getName(_registerState.state)}', - style: TextStyle(fontSize: 18, color: Colors.black54), - )), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 18.0, 48.0, 0), - child: Align( - child: Text('WebSocket:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _wsUriController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - ), - ), - ), - ], - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46.0, 18.0, 48.0, 0), - child: Align( - child: Text('SIP URI:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _sipUriController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - ), - ), - ), - ], - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46.0, 18.0, 48.0, 0), - child: Align( - child: Text('Authorization User:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _authorizationUserController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - hintText: _authorizationUserController.text.isEmpty - ? '[Empty]' - : null, - ), - ), - ), - ], - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46.0, 18.0, 48.0, 0), - child: Align( - child: Text('Password:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _passwordController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - hintText: _passwordController.text.isEmpty - ? '[Empty]' - : null, - ), - ), - ), - ], - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46.0, 18.0, 48.0, 0), - child: Align( - child: Text('Display Name:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _displayNameController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - ), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.fromLTRB(0.0, 18.0, 0.0, 0.0), - child: Container( - height: 48.0, - width: 160.0, - child: MaterialButton( - child: Text( - 'Register', - style: - TextStyle(fontSize: 16.0, color: Colors.white), - ), - color: Colors.blue, - textColor: Colors.white, - onPressed: () => _handleSave(context), - ), - )) - ]))); + appBar: AppBar( + title: Text("SIP Account"), + ), + body: ListView( + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), + children: [ + Center( + child: Text( + 'Register Status: ${EnumHelper.getName(_registerState.state)}', + style: TextStyle(fontSize: 18, color: Colors.black54), + ), + ), + SizedBox(height: 40), + Text('WebSocket:'), + TextFormField( + controller: _wsUriController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + ), + SizedBox(height: 20), + Text('SIP URI:'), + TextFormField( + controller: _sipUriController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + ), + SizedBox(height: 20), + Text('Authorization User:'), + TextFormField( + controller: _authorizationUserController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: + _authorizationUserController.text.isEmpty ? '[Empty]' : null, + ), + ), + SizedBox(height: 20), + Text('Password:'), + TextFormField( + controller: _passwordController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: _passwordController.text.isEmpty ? '[Empty]' : null, + ), + ), + SizedBox(height: 20), + Text('Display Name:'), + TextFormField( + controller: _displayNameController, + keyboardType: TextInputType.text, + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: _displayNameController.text.isEmpty ? '[Empty]' : null, + ), + ), + const SizedBox(height: 40), + ElevatedButton( + child: Text('Register'), + onPressed: () => _handleSave(context), + ), + ], + ), + ); } @override diff --git a/example/lib/src/widgets/action_button.dart b/example/lib/src/widgets/action_button.dart index ca2bf08f..d2a2cec3 100644 --- a/example/lib/src/widgets/action_button.dart +++ b/example/lib/src/widgets/action_button.dart @@ -23,7 +23,7 @@ class ActionButton extends StatefulWidget { : super(key: key); @override - _ActionButtonState createState() => _ActionButtonState(); + State createState() => _ActionButtonState(); } class _ActionButtonState extends State { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e94de160..611b9d42 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -21,19 +21,16 @@ environment: dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 sip_ua: path: ../ - shared_preferences: ^2.0.5 - permission_handler: ^9.2.0 + shared_preferences: ^2.2.0 + permission_handler: ^10.4.3 + flutter_webrtc: ^0.9.40 dev_dependencies: flutter_test: sdk: flutter - lints: ^1.0.1 + lints: ^2.1.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index cef21ac5..49d4d152 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -88,7 +88,9 @@ class RTCSession extends EventManager implements Owner { final Map _earlyDialogs = {}; String? _contact; String? _from_tag; + String? get from_tag => _from_tag; String? _to_tag; + String? get to_tag => _to_tag; // The RTCPeerConnection instance (public attribute). RTCPeerConnection? _connection; @@ -360,7 +362,15 @@ class RTCSession extends EventManager implements Owner { // Get the Expires header value if exists. if (request.hasHeader('expires')) { - expires = request.getHeader('expires') * 1000; + try { + expires = (request.getHeader('expires') is num + ? request.getHeader('expires') + : num.tryParse(request.getHeader('expires'))!) * + 1000; + } catch (e) { + logger.e( + 'Invalid Expires header value: ${request.getHeader('expires')}, error $e'); + } } /* Set the to_tag before diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index aaf6e56f..f5980a66 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -141,6 +141,8 @@ class SIPUAHelper extends EventManager { _settings.ice_gathering_timeout = uaSettings.iceGatheringTimeout; _settings.session_timers_refresh_method = uaSettings.sessionTimersRefreshMethod; + _settings.instance_id = uaSettings.instanceId; + _settings.registrar_server = uaSettings.registrarServer; try { _ua = UA(_settings); @@ -709,6 +711,8 @@ class UaSettings { String? password; String? ha1; String? displayName; + String? instanceId; + String? registrarServer; /// DTMF mode, in band (rfc2833) or out of band (sip info) DtmfMode dtmfMode = DtmfMode.INFO;