Commit 94aa9451 authored by Inomjon's avatar Inomjon

Call cannection to app

parent aa970a55
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
......@@ -12,6 +6,11 @@ if (localPropertiesFile.exists()) {
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
......@@ -22,33 +21,28 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "uz.technounitgroup.vmeeting"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
kotlinOptions {
jvmTarget = '1.8'
}
android {
compileSdkVersion 33
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "uz.technounitgroup.vmeeting"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
minSdkVersion 21
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
......@@ -56,12 +50,29 @@ android {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
flutter {
source '../..'
}
dependencies {}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
apply plugin: 'com.google.gms.google-services'
{
"project_info": {
"project_number": "130671191292",
"project_id": "connectycubevideocall",
"storage_bucket": "connectycubevideocall.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:130671191292:android:52bb65faf37241024ba379",
"android_client_info": {
"package_name": "uz.technounitgroup.vmeeting"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyD59k2Vh3nVTxnuRLMZW-AUHPmjN9Yr1uA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
\ No newline at end of file
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uz.technounitgroup.vmeeting">
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="vmeeting"
android:name="${applicationName}"
......
......@@ -6,8 +6,9 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.15'
}
}
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyCF92QjQTaDqzw4iVhvSmNEEc9aJSiRa-0</string>
<key>GCM_SENDER_ID</key>
<string>130671191292</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>uz.technounitgroup.vmeeting</string>
<key>PROJECT_ID</key>
<string>connectycubevideocall</string>
<key>STORAGE_BUCKET</key>
<string>connectycubevideocall.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:130671191292:ios:03706795dbbaa44b4ba379</string>
</dict>
</plist>
\ No newline at end of file
{
"file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:130671191292:ios:03706795dbbaa44b4ba379",
"FIREBASE_PROJECT_ID": "connectycubevideocall",
"GCM_SENDER_ID": "130671191292"
}
\ No newline at end of file
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyAzcw2Ts_AoBaqWukPKQxPriza3Dexhcbg',
appId: '1:130671191292:web:4dedaaa6a1c367744ba379',
messagingSenderId: '130671191292',
projectId: 'connectycubevideocall',
authDomain: 'connectycubevideocall.firebaseapp.com',
storageBucket: 'connectycubevideocall.appspot.com',
measurementId: 'G-FHPHE2G7VV',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyD59k2Vh3nVTxnuRLMZW-AUHPmjN9Yr1uA',
appId: '1:130671191292:android:52bb65faf37241024ba379',
messagingSenderId: '130671191292',
projectId: 'connectycubevideocall',
storageBucket: 'connectycubevideocall.appspot.com',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyCF92QjQTaDqzw4iVhvSmNEEc9aJSiRa-0',
appId: '1:130671191292:ios:03706795dbbaa44b4ba379',
messagingSenderId: '130671191292',
projectId: 'connectycubevideocall',
storageBucket: 'connectycubevideocall.appspot.com',
iosBundleId: 'uz.technounitgroup.vmeeting',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'AIzaSyCF92QjQTaDqzw4iVhvSmNEEc9aJSiRa-0',
appId: '1:130671191292:ios:608d88189b2e4fd64ba379',
messagingSenderId: '130671191292',
projectId: 'connectycubevideocall',
storageBucket: 'connectycubevideocall.appspot.com',
iosBundleId: 'uz.technounitgroup.vmeeting.RunnerTests',
);
}
import 'package:connectycube_flutter_call_kit/connectycube_flutter_call_kit.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'package:flutter/services.dart';
import 'package:vmeeting/src/login_screen.dart';
import 'package:vmeeting/src/utils/pref_util.dart';
import 'firebase_options.dart';
import 'src/utils/configs.dart' as config;
void main() {
runApp(const MyApp());
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
));
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(App());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
class App extends StatefulWidget {
const App({super.key});
// This widget is the root of your application.
@override
State<StatefulWidget> createState() {
return _AppState();
}
}
class _AppState extends State<App> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a blue toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
primarySwatch: Colors.green,
),
home: Builder(
builder: (context) {
return LoginScreen();
},
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
@override
void initState() {
super.initState();
final String title;
ConnectycubeFlutterCallKit.instance.init();
@override
State<MyHomePage> createState() => _MyHomePageState();
initConnectycube();
}
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
initConnectycube() {
init(
config.APP_ID,
config.AUTH_KEY,
config.AUTH_SECRET,
onSessionRestore: () {
return SharedPrefs.getUser().then((savedUser) {
return createSession(savedUser);
});
},
);
}
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
initConnectycubeContextLess() {
CubeSettings.instance.applicationId = config.APP_ID;
CubeSettings.instance.authorizationKey = config.AUTH_KEY;
CubeSettings.instance.authorizationSecret = config.AUTH_SECRET;
CubeSettings.instance.onSessionRestore = () {
return SharedPrefs.getUser().then((savedUser) {
return createSession(savedUser);
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
};
}
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:universal_io/io.dart';
import 'package:vmeeting/src/utils/configs.dart';
import 'package:web_browser_detect/web_browser_detect.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'login_screen.dart';
import 'managers/call_manager.dart';
import 'utils/platform_utils.dart';
class ConversationCallScreen extends StatefulWidget {
final P2PSession _callSession;
final bool _isIncoming;
@override
State<StatefulWidget> createState() {
return _ConversationCallScreenState(_callSession, _isIncoming);
}
ConversationCallScreen(this._callSession, this._isIncoming);
}
class _ConversationCallScreenState extends State<ConversationCallScreen>
implements RTCSessionStateCallback<P2PSession> {
static const String TAG = "_ConversationCallScreenState";
final P2PSession _callSession;
final bool _isIncoming;
final CubeStatsReportsManager _statsReportsManager =
CubeStatsReportsManager();
bool _isCameraEnabled = true;
bool _isSpeakerEnabled = Platform.isIOS ? false : true;
bool _isMicMute = false;
bool _isFrontCameraUsed = true;
final int _currentUserId = CubeChatConnection.instance.currentUser!.id!;
MapEntry<int, RTCVideoRenderer>? primaryRenderer;
Map<int, RTCVideoRenderer> minorRenderers = {};
RTCVideoViewObjectFit primaryVideoFit =
RTCVideoViewObjectFit.RTCVideoViewObjectFitCover;
bool _enableScreenSharing;
_ConversationCallScreenState(this._callSession, this._isIncoming)
: _enableScreenSharing = !_callSession.startScreenSharing;
@override
void initState() {
super.initState();
_initAlreadyReceivedStreams();
_callSession.onLocalStreamReceived = _addLocalMediaStream;
_callSession.onRemoteStreamReceived = _addRemoteMediaStream;
_callSession.onSessionClosed = _onSessionClosed;
_statsReportsManager.init(_callSession);
_callSession.setSessionCallbacksListener(this);
if (_isIncoming) {
if (_callSession.state == RTCSessionState.RTC_SESSION_NEW) {
_callSession.acceptCall();
}
} else {
_callSession.startCall();
}
CallManager.instance.onMicMuted = (muted, sessionId) {
setState(() {
_isMicMute = muted;
_callSession.setMicrophoneMute(_isMicMute);
});
};
}
@override
void dispose() {
super.dispose();
stopBackgroundExecution();
primaryRenderer?.value.srcObject = null;
primaryRenderer?.value.dispose();
minorRenderers.forEach((opponentId, renderer) {
log("[dispose] dispose renderer for $opponentId", TAG);
try {
renderer.srcObject?.dispose();
renderer.srcObject = null;
renderer.dispose();
} catch (e) {
log('Error $e');
}
});
}
Future<void> _addLocalMediaStream(MediaStream stream) async {
log("_addLocalMediaStream, stream Id: ${stream.id}", TAG);
_addMediaStream(_currentUserId, stream);
}
void _addRemoteMediaStream(session, int userId, MediaStream stream) {
log("_addRemoteMediaStream for user $userId", TAG);
_addMediaStream(userId, stream);
}
Future<void> _removeMediaStream(callSession, int userId) async {
log("_removeMediaStream for user $userId", TAG);
var videoRenderer = minorRenderers[userId];
if (videoRenderer == null) return;
videoRenderer.srcObject = null;
videoRenderer.dispose();
setState(() {
minorRenderers.remove(userId);
});
}
Future<void> _addMediaStream(int userId, MediaStream stream) async {
if (primaryRenderer == null) {
primaryRenderer = MapEntry(userId, RTCVideoRenderer());
await primaryRenderer!.value.initialize();
setState(() {
primaryRenderer?.value.srcObject = stream;
});
return;
}
if (minorRenderers[userId] == null) {
minorRenderers[userId] = RTCVideoRenderer();
await minorRenderers[userId]?.initialize();
}
setState(() {
minorRenderers[userId]?.srcObject = stream;
if (primaryRenderer?.key == _currentUserId ||
primaryRenderer?.key == userId) {
_replacePrimaryRenderer(userId);
}
});
}
void _replacePrimaryRenderer(int newPrimaryUser) {
if (primaryRenderer?.key != newPrimaryUser) {
minorRenderers.addEntries([primaryRenderer!]);
}
primaryRenderer =
MapEntry(newPrimaryUser, minorRenderers.remove(newPrimaryUser)!);
}
void _onSessionClosed(session) {
log("_onSessionClosed", TAG);
_callSession.removeSessionCallbacksListener();
_statsReportsManager.dispose();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
}
Widget buildMinorVideoItem(int opponentId, RTCVideoRenderer renderer) {
return Expanded(
child: Stack(
children: [
RTCVideoView(
renderer,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
mirror: false,
),
Align(
alignment: Alignment.centerLeft,
child: Container(
margin: EdgeInsets.all(8),
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 10,
),
child: RotatedBox(
quarterTurns: -1,
child: StreamBuilder<CubeMicLevelEvent>(
stream: _statsReportsManager.micLevelStream
.where((event) => event.userId == opponentId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return LinearProgressIndicator(value: 0);
} else {
var micLevelForUser = snapshot.data!;
return LinearProgressIndicator(
value: micLevelForUser.micLevel);
}
},
),
),
),
)),
Align(
alignment: Alignment.topCenter,
child: Container(
margin: EdgeInsets.only(top: 8),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Container(
padding: EdgeInsets.all(8),
color: Colors.black26,
child: StreamBuilder<CubeVideoBitrateEvent>(
stream: _statsReportsManager.videoBitrateStream
.where((event) => event.userId == opponentId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text(
'0 kbits/sec',
style: TextStyle(color: Colors.white),
);
} else {
var videoBitrateForUser = snapshot.data!;
return Text(
'${videoBitrateForUser.bitRate} kbits/sec',
style: TextStyle(color: Colors.white),
);
}
},
),
),
),
))
],
),
);
}
List<Widget> renderStreamsGrid(Orientation orientation) {
List<Widget> streamsExpanded = [];
if (primaryRenderer != null) {
streamsExpanded.add(Expanded(
child: RTCVideoView(
primaryRenderer!.value,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
mirror: true,
)));
}
if (CallManager.instance.remoteStreams.isNotEmpty) {
minorRenderers.addEntries([
...CallManager.instance.remoteStreams.entries.map((mediaStreamEntry) {
var videoRenderer = RTCVideoRenderer();
videoRenderer.initialize().then((value) {
videoRenderer.srcObject = mediaStreamEntry.value;
});
return MapEntry(mediaStreamEntry.key, videoRenderer);
})
]);
CallManager.instance.remoteStreams.clear();
}
streamsExpanded.addAll(minorRenderers.entries
.map(
(entry) => buildMinorVideoItem(entry.key, entry.value),
)
.toList());
if (streamsExpanded.length > 2) {
List<Widget> rows = [];
for (var i = 0; i < streamsExpanded.length; i += 2) {
var chunkEndIndex = i + 2;
if (streamsExpanded.length < chunkEndIndex) {
chunkEndIndex = streamsExpanded.length;
}
var chunk = streamsExpanded.sublist(i, chunkEndIndex);
rows.add(
Expanded(
child: orientation == Orientation.portrait
? Row(children: chunk)
: Column(children: chunk),
),
);
}
return rows;
}
return streamsExpanded;
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onBackPressed(context),
child: Scaffold(
backgroundColor: Colors.grey,
body: Stack(fit: StackFit.loose, clipBehavior: Clip.none, children: [
_isVideoCall()
? OrientationBuilder(
builder: (context, orientation) {
return _callSession.opponentsIds.length > 1
? _buildGroupCallLayout(orientation)
: _buildPrivateCallLayout(orientation);
},
)
: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 24),
child: Text(
"Audio call",
style: TextStyle(fontSize: 28),
),
),
Padding(
padding: EdgeInsets.only(bottom: 12),
child: Text(
"Members:",
style: TextStyle(
fontSize: 20, fontStyle: FontStyle.italic),
),
),
Text(
_callSession.opponentsIds.join(", "),
style: TextStyle(fontSize: 20),
),
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: _getActionsPanel(),
),
]),
),
);
}
Widget _buildGroupCallLayout(Orientation orientation) {
return Center(
child: Container(
child: orientation == Orientation.portrait
? Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: renderGroupCallViews(orientation))
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: renderGroupCallViews(orientation)),
),
);
}
Widget _buildPrivateCallLayout(Orientation orientation) {
return Container(
child: Stack(children: [
if (primaryRenderer != null) _buildPrimaryVideoView(orientation),
if (minorRenderers.isNotEmpty)
Align(
alignment: Alignment.topRight,
child: Padding(
padding: orientation == Orientation.portrait
? EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 10,
right: MediaQuery.of(context).padding.right + 10)
: EdgeInsets.only(
right: MediaQuery.of(context).padding.right + 10,
top: MediaQuery.of(context).padding.top + 10),
child: buildItems(
minorRenderers,
orientation == Orientation.portrait
? MediaQuery.of(context).size.width / 3
: MediaQuery.of(context).size.width / 4,
orientation == Orientation.portrait
? MediaQuery.of(context).size.height / 4
: MediaQuery.of(context).size.height / 2.5)
.first,
))
]),
);
}
List<Widget> renderGroupCallViews(Orientation orientation) {
List<Widget> streamsExpanded = [];
if (primaryRenderer != null) {
streamsExpanded.add(
Expanded(flex: 3, child: _buildPrimaryVideoView(orientation)),
);
}
var itemHeight;
var itemWidth;
if (orientation == Orientation.portrait) {
itemHeight = MediaQuery.of(context).size.height / 3 * 0.8;
itemWidth = itemHeight / 3 * 4;
} else {
itemWidth = MediaQuery.of(context).size.width / 3 * 0.8;
itemHeight = itemWidth / 4 * 3;
}
var minorItems = buildItems(minorRenderers, itemWidth, itemHeight);
if (minorRenderers.isNotEmpty) {
var membersList = Expanded(
flex: 1,
child: ListView(
scrollDirection: orientation == Orientation.landscape
? Axis.vertical
: Axis.horizontal,
children: minorItems,
),
);
streamsExpanded.add(membersList);
}
return streamsExpanded;
}
List<Widget> buildItems(Map<int, RTCVideoRenderer> renderers,
double itemWidth, double itemHeight) {
return renderers.entries
.map(
(entry) => GestureDetector(
onTap: () {
setState(() {
_replacePrimaryRenderer(entry.key);
});
},
child: AbsorbPointer(
child: Container(
width: itemWidth,
height: itemHeight,
padding: EdgeInsets.all(4),
child: Stack(
children: [
StreamBuilder<CubeMicLevelEvent>(
stream: _statsReportsManager.micLevelStream
.where((event) => event.userId == entry.key),
builder: (context, snapshot) {
var width =
!snapshot.hasData ? 0 : snapshot.data!.micLevel * 4;
return Container(
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6.0),
side: BorderSide(
width: width.toDouble(),
color: Colors.green,
strokeAlign: 1.0),
),
),
);
},
),
ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: RTCVideoView(
entry.value,
objectFit:
RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
mirror: entry.key == _currentUserId &&
_isFrontCameraUsed &&
_enableScreenSharing,
),
),
if (entry.key != _currentUserId)
Align(
alignment: Alignment.topCenter,
child: Container(
margin: EdgeInsets.only(top: 8),
child: ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(12)),
child: Container(
padding: EdgeInsets.all(6),
color: Colors.black26,
child: StreamBuilder<CubeVideoBitrateEvent>(
stream: _statsReportsManager
.videoBitrateStream
.where(
(event) => event.userId == entry.key),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text(
'0 kbits/sec',
style: TextStyle(color: Colors.white),
);
} else {
var videoBitrateForUser = snapshot.data!;
return Text(
'${videoBitrateForUser.bitRate} kbits/sec',
style: TextStyle(color: Colors.white),
);
}
},
),
),
),
)),
Align(
alignment: Alignment.bottomCenter,
child: Container(
margin: EdgeInsets.only(bottom: 8),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Container(
padding: EdgeInsets.all(6),
color: Colors.black26,
child: Text(
entry.key ==
CubeChatConnection
.instance.currentUser?.id
? 'Me'
: users
.where((user) => user.id == entry.key)
.first
.fullName ??
'Unknown',
style: TextStyle(color: Colors.white),
),
),
),
),
),
],
),
),
),
),
)
.toList();
}
Widget _buildPrimaryVideoView(Orientation orientation) {
return Stack(
children: [
RTCVideoView(
primaryRenderer!.value,
objectFit: primaryVideoFit,
mirror: primaryRenderer!.key == _currentUserId &&
_isFrontCameraUsed &&
_enableScreenSharing,
),
Align(
alignment: Alignment.centerLeft,
child: StreamBuilder<CubeMicLevelEvent>(
stream: _statsReportsManager.micLevelStream
.where((event) => event.userId == primaryRenderer!.key),
builder: (context, snapshot) {
return Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 50,
left: MediaQuery.of(context).padding.left + 15,
bottom: MediaQuery.of(context).padding.bottom + 100),
child: RotatedBox(
quarterTurns: -1,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 200),
child: LinearProgressIndicator(
value: !snapshot.hasData ? 0 : snapshot.data!.micLevel,
),
),
));
},
),
),
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: orientation == Orientation.portrait
? EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 10,
left: MediaQuery.of(context).padding.left + 10)
: EdgeInsets.only(
left: MediaQuery.of(context).padding.left + 10,
top: MediaQuery.of(context).padding.top + 10),
child: FloatingActionButton(
elevation: 0,
heroTag: "ToggleScreenFit",
child: Icon(
primaryVideoFit ==
RTCVideoViewObjectFit.RTCVideoViewObjectFitCover
? Icons.zoom_in_map
: Icons.zoom_out_map,
color: Colors.white,
),
onPressed: () => _switchPrimaryVideoFit(),
backgroundColor: Colors.black38,
),
),
),
if (primaryRenderer!.key != _currentUserId)
Align(
alignment: Alignment.topCenter,
child: Container(
margin:
EdgeInsets.only(top: MediaQuery.of(context).padding.top + 10),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Container(
padding: EdgeInsets.all(6),
color: Colors.black26,
child: StreamBuilder<CubeVideoBitrateEvent>(
stream: _statsReportsManager.videoBitrateStream
.where((event) => event.userId == primaryRenderer!.key),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text(
'0 kbits/sec',
style: TextStyle(color: Colors.white),
);
} else {
var videoBitrateForUser = snapshot.data!;
return Text(
'${videoBitrateForUser.bitRate} kbits/sec',
style: TextStyle(color: Colors.white),
);
}
},
),
),
),
),
),
],
);
}
_switchPrimaryVideoFit() async {
setState(() {
primaryVideoFit =
primaryVideoFit == RTCVideoViewObjectFit.RTCVideoViewObjectFitCover
? RTCVideoViewObjectFit.RTCVideoViewObjectFitContain
: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover;
});
}
Widget _getActionsPanel() {
return Container(
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 8,
left: MediaQuery.of(context).padding.left + 8,
right: MediaQuery.of(context).padding.right + 8),
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(32),
bottomRight: Radius.circular(32),
topLeft: Radius.circular(32),
topRight: Radius.circular(32)),
child: Container(
padding: EdgeInsets.all(4),
color: Colors.black26,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 4),
child: FloatingActionButton(
elevation: 0,
heroTag: "Mute",
child: Icon(
_isMicMute ? Icons.mic_off : Icons.mic,
color: _isMicMute ? Colors.grey : Colors.white,
),
onPressed: () => _muteMic(),
backgroundColor: Colors.black38,
),
),
Visibility(
visible: _enableScreenSharing,
child: Padding(
padding: EdgeInsets.only(right: 4),
child: FloatingActionButton(
elevation: 0,
heroTag: "ToggleCamera",
child: Icon(
_isVideoEnabled() ? Icons.videocam : Icons.videocam_off,
color: _isVideoEnabled() ? Colors.white : Colors.grey,
),
onPressed: () => _toggleCamera(),
backgroundColor: Colors.black38,
),
),
),
SpeedDial(
heroTag: "Options",
icon: Icons.more_vert,
activeIcon: Icons.close,
backgroundColor: Colors.black38,
switchLabelPosition: true,
overlayColor: Colors.black,
elevation: 0,
overlayOpacity: 0.5,
children: [
SpeedDialChild(
elevation: 0,
child: Icon(
_enableScreenSharing
? Icons.screen_share
: Icons.stop_screen_share,
color: Colors.white,
),
backgroundColor: Colors.black38,
foregroundColor: Colors.white,
label:
'${_enableScreenSharing ? 'Start' : 'Stop'} Screen Sharing',
onTap: () => _toggleScreenSharing(),
),
SpeedDialChild(
elevation: 0,
visible: !(kIsWeb &&
(Browser().browserAgent == BrowserAgent.Safari ||
Browser().browserAgent == BrowserAgent.Firefox)),
child: Icon(
kIsWeb || WebRTC.platformIsDesktop
? Icons.surround_sound
: _isSpeakerEnabled
? Icons.volume_up
: Icons.volume_off,
color: _isSpeakerEnabled ? Colors.white : Colors.grey,
),
backgroundColor: Colors.black38,
foregroundColor: Colors.white,
label:
'Switch ${kIsWeb || WebRTC.platformIsDesktop ? 'Audio output' : 'Speakerphone'}',
onTap: () => _switchSpeaker(),
),
SpeedDialChild(
elevation: 0,
visible: kIsWeb || WebRTC.platformIsDesktop,
child: Icon(
Icons.record_voice_over,
color: Colors.white,
),
backgroundColor: Colors.black38,
foregroundColor: Colors.white,
label: 'Switch Audio Input device',
onTap: () => _switchAudioInput(),
),
SpeedDialChild(
elevation: 0,
visible: _enableScreenSharing,
child: Icon(
Icons.cameraswitch,
color: _isVideoEnabled() ? Colors.white : Colors.grey,
),
backgroundColor: Colors.black38,
foregroundColor: Colors.white,
label: 'Switch Camera',
onTap: () => _switchCamera(),
),
],
),
Expanded(
child: SizedBox(),
flex: 1,
),
Padding(
padding: EdgeInsets.only(left: 0),
child: FloatingActionButton(
child: Icon(
Icons.call_end,
color: Colors.white,
),
backgroundColor: Colors.red,
onPressed: () => _endCall(),
),
),
],
),
),
),
);
}
_endCall() {
CallManager.instance.hungUp();
}
Future<bool> _onBackPressed(BuildContext context) {
return Future.value(false);
}
_muteMic() {
setState(() {
_isMicMute = !_isMicMute;
_callSession.setMicrophoneMute(_isMicMute);
CallManager.instance.muteCall(_callSession.sessionId, _isMicMute);
});
}
_switchCamera() {
if (!_isVideoEnabled()) return;
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
_callSession.switchCamera().then((isFrontCameraUsed) {
setState(() {
_isFrontCameraUsed = isFrontCameraUsed;
});
});
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return FutureBuilder<List<MediaDeviceInfo>>(
future: _callSession.getCameras(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return AlertDialog(
content: const Text('No cameras found'),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
} else {
return SimpleDialog(
title: const Text('Select camera'),
children: snapshot.data?.map(
(mediaDeviceInfo) {
return SimpleDialogOption(
onPressed: () {
Navigator.pop(context, mediaDeviceInfo.deviceId);
},
child: Text(mediaDeviceInfo.label),
);
},
).toList(),
);
}
},
);
},
).then((deviceId) {
log("onCameraSelected deviceId: $deviceId", TAG);
if (deviceId != null) _callSession.switchCamera(deviceId: deviceId);
});
}
}
_toggleCamera() {
if (!_isVideoCall()) return;
setState(() {
_isCameraEnabled = !_isCameraEnabled;
_callSession.setVideoEnabled(_isCameraEnabled);
});
}
_toggleScreenSharing() async {
var foregroundServiceFuture = _enableScreenSharing
? startBackgroundExecution()
: stopBackgroundExecution();
var hasPermissions = await hasBackgroundExecutionPermissions();
if (!hasPermissions) {
await initForegroundService();
}
var desktopCapturerSource = _enableScreenSharing && isDesktop
? await showDialog<DesktopCapturerSource>(
context: context,
builder: (context) => ScreenSelectDialog(),
)
: null;
foregroundServiceFuture.then((_) {
_callSession
.enableScreenSharing(_enableScreenSharing,
desktopCapturerSource: desktopCapturerSource,
useIOSBroadcasting: true,
requestAudioForScreenSharing: true)
.then((voidResult) {
setState(() {
_enableScreenSharing = !_enableScreenSharing;
_isFrontCameraUsed = _enableScreenSharing;
});
});
});
}
bool _isVideoEnabled() {
return _isVideoCall() && _isCameraEnabled;
}
bool _isVideoCall() {
return CallType.VIDEO_CALL == _callSession.callType;
}
_switchSpeaker() {
if (kIsWeb || WebRTC.platformIsDesktop) {
showDialog(
context: context,
builder: (BuildContext context) {
return FutureBuilder<List<MediaDeviceInfo>>(
future: _callSession.getAudioOutputs(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return AlertDialog(
content: const Text('No Audio output devices found'),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
} else {
return SimpleDialog(
title: const Text('Select Audio output device'),
children: snapshot.data?.map(
(mediaDeviceInfo) {
return SimpleDialogOption(
onPressed: () {
Navigator.pop(context, mediaDeviceInfo.deviceId);
},
child: Text(mediaDeviceInfo.label),
);
},
).toList(),
);
}
},
);
},
).then((deviceId) {
log("onAudioOutputSelected deviceId: $deviceId", TAG);
if (deviceId != null) {
setState(() {
if (kIsWeb) {
primaryRenderer?.value.audioOutput(deviceId);
minorRenderers.forEach((userId, renderer) {
renderer.audioOutput(deviceId);
});
} else {
_callSession.selectAudioOutput(deviceId);
}
});
}
});
} else {
setState(() {
_isSpeakerEnabled = !_isSpeakerEnabled;
_callSession.enableSpeakerphone(_isSpeakerEnabled);
});
}
}
_switchAudioInput() {
if (kIsWeb || WebRTC.platformIsDesktop) {
showDialog(
context: context,
builder: (BuildContext context) {
return FutureBuilder<List<MediaDeviceInfo>>(
future: _callSession.getAudioInputs(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return AlertDialog(
content: const Text('No Audio input devices found'),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
} else {
return SimpleDialog(
title: const Text('Select Audio input device'),
children: snapshot.data?.map(
(mediaDeviceInfo) {
return SimpleDialogOption(
onPressed: () {
Navigator.pop(context, mediaDeviceInfo.deviceId);
},
child: Text(mediaDeviceInfo.label),
);
},
).toList(),
);
}
},
);
},
).then((deviceId) {
log("onAudioOutputSelected deviceId: $deviceId", TAG);
if (deviceId != null) {
setState(() {
_callSession.selectAudioInput(deviceId);
});
}
});
}
}
@override
void onConnectedToUser(P2PSession session, int userId) {
log("onConnectedToUser userId= $userId");
}
@override
void onConnectionClosedForUser(P2PSession session, int userId) {
log("onConnectionClosedForUser userId= $userId");
_removeMediaStream(session, userId);
}
@override
void onDisconnectedFromUser(P2PSession session, int userId) {
log("onDisconnectedFromUser userId= $userId");
}
void _initAlreadyReceivedStreams() {
if (CallManager.instance.remoteStreams.isNotEmpty) {
minorRenderers.addEntries([
...CallManager.instance.remoteStreams.entries.map((mediaStreamEntry) {
var videoRenderer = RTCVideoRenderer();
videoRenderer.initialize().then((value) {
videoRenderer.srcObject = mediaStreamEntry.value;
});
return MapEntry(mediaStreamEntry.key, videoRenderer);
})
]);
// CallManager.instance.remoteStreams
// .clear(); //TODO VT check concurrency issue
}
createLocalRenderer() {
var renderer = MapEntry(_currentUserId, RTCVideoRenderer());
renderer.value.initialize().then((value) {
renderer.value.srcObject = CallManager.instance.localMediaStream;
});
return renderer;
}
if (CallManager.instance.localMediaStream != null) {
if (minorRenderers.isNotEmpty) {
var tempPrimaryRenderer = minorRenderers.entries.first;
primaryRenderer = tempPrimaryRenderer;
minorRenderers.remove(tempPrimaryRenderer.key);
minorRenderers.addEntries([createLocalRenderer()]);
} else {
primaryRenderer = createLocalRenderer();
// CallManager.instance.localMediaStream = null;
}
}
}
@override
void onConnectingToUser(P2PSession session, int userId) {
// TODO: implement onConnectingToUser
}
@override
void onConnectionFailedWithUser(P2PSession session, int userId) {
// TODO: implement onConnectionFailedWithUser
}
}
import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'managers/call_manager.dart';
class IncomingCallScreen extends StatelessWidget {
static const String TAG = "IncomingCallScreen";
final P2PSession _callSession;
IncomingCallScreen(this._callSession);
@override
Widget build(BuildContext context) {
_callSession.onSessionClosed = (callSession) {
log("_onSessionClosed", TAG);
Navigator.pop(context);
};
return WillPopScope(
onWillPop: () => _onBackPressed(context),
child: Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(36),
child: Text(_getCallTitle(), style: TextStyle(fontSize: 28)),
),
Padding(
padding: EdgeInsets.only(top: 36, bottom: 8),
child: Text("Members:", style: TextStyle(fontSize: 20)),
),
Padding(
padding: EdgeInsets.only(bottom: 86),
child: Text(_callSession.opponentsIds.join(", "),
style: TextStyle(fontSize: 18)),
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 36),
child: FloatingActionButton(
heroTag: "RejectCall",
child: Icon(
Icons.call_end,
color: Colors.white,
),
backgroundColor: Colors.red,
onPressed: () => _rejectCall(context, _callSession),
),
),
Padding(
padding: EdgeInsets.only(left: 36),
child: FloatingActionButton(
heroTag: "AcceptCall",
child: Icon(
Icons.call,
color: Colors.white,
),
backgroundColor: Colors.green,
onPressed: () => _acceptCall(context, _callSession),
),
),
],
),
],
),
)));
}
_getCallTitle() {
var callType;
switch (_callSession.callType) {
case CallType.VIDEO_CALL:
callType = "Video";
break;
case CallType.AUDIO_CALL:
callType = "Audio";
break;
}
return "Incoming $callType call";
}
void _acceptCall(BuildContext context, P2PSession callSession) {
CallManager.instance.acceptCall(callSession.sessionId, false);
}
void _rejectCall(BuildContext context, P2PSession callSession) {
CallManager.instance.reject(callSession.sessionId, false);
}
Future<bool> _onBackPressed(BuildContext context) {
return Future.value(false);
}
}
import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'select_opponents_screen.dart';
import 'utils/configs.dart' as utils;
import 'utils/pref_util.dart';
class LoginScreen extends StatelessWidget {
static const String TAG = "LoginScreen";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:
AppBar(automaticallyImplyLeading: false, title: Text('P2P calls')),
body: BodyLayout(),
);
}
}
class BodyLayout extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return BodyState();
}
}
class BodyState extends State<BodyLayout> {
static const String TAG = "LoginScreen.BodyState";
bool _isLoginContinues = false;
int? _selectedUserId;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(48),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Select user to login:",
style: TextStyle(
fontSize: 22,
),
),
Expanded(
child: _getUsersList(context),
),
],
),
);
}
@override
void initState() {
super.initState();
SharedPrefs.getUser().then((loggedUser) {
if (loggedUser != null) {
_loginToCC(context, loggedUser);
}
});
}
Widget _getUsersList(BuildContext context) {
final users = utils.users;
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return Card(
color: _isLoginContinues ? Colors.white70 : Colors.white,
child: ListTile(
title: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
users[index].fullName!,
style: TextStyle(
color: _isLoginContinues
? Colors.black26
: Colors.black87),
),
Container(
margin: EdgeInsets.only(left: 8),
height: 18,
width: 18,
child: Visibility(
visible: _isLoginContinues &&
users[index].id == _selectedUserId,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
],
),
),
onTap: () => _loginToCC(
context,
users[index],
),
),
);
},
);
}
_loginToCC(BuildContext context, CubeUser user) {
if (_isLoginContinues) return;
setState(() {
_isLoginContinues = true;
_selectedUserId = user.id;
});
if (CubeSessionManager.instance.isActiveSessionValid() &&
CubeSessionManager.instance.activeSession!.user != null) {
if (CubeChatConnection.instance.isAuthenticated()) {
setState(() {
_isLoginContinues = false;
_selectedUserId = 0;
});
_goSelectOpponentsScreen(context, user);
} else {
_loginToCubeChat(context, user);
}
} else {
createSession(user).then((cubeSession) {
_loginToCubeChat(context, user);
}).catchError((exception) {
_processLoginError(exception);
});
}
}
void _loginToCubeChat(BuildContext context, CubeUser user) {
CubeChatConnection.instance.login(user).then((cubeUser) {
SharedPrefs.saveNewUser(user);
setState(() {
_isLoginContinues = false;
_selectedUserId = 0;
});
_goSelectOpponentsScreen(context, cubeUser);
}).catchError((exception) {
_processLoginError(exception);
});
}
void _processLoginError(exception) {
log("Login error $exception", TAG);
setState(() {
_isLoginContinues = false;
_selectedUserId = 0;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Login Error"),
content: Text("Something went wrong during login to ConnectyCube"),
actions: <Widget>[
TextButton(
child: Text("OK"),
onPressed: () => Navigator.of(context).pop(),
)
],
);
});
}
void _goSelectOpponentsScreen(BuildContext context, CubeUser cubeUser) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SelectOpponentsScreen(cubeUser),
),
);
}
}
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'package:universal_io/io.dart';
import 'package:connectycube_flutter_call_kit/connectycube_flutter_call_kit.dart';
class CallKitManager {
static CallKitManager get instance => _getInstance();
static CallKitManager? _instance;
static String TAG = "CallKitManager";
static CallKitManager _getInstance() {
return _instance ??= CallKitManager._internal();
}
factory CallKitManager() => _getInstance();
CallKitManager._internal();
late Function(String uuid) onCallAccepted;
late Function(String uuid) onCallEnded;
late Function(bool mute, String uuid) onMuteCall;
init({
required onCallAccepted(uuid),
required onCallEnded(uuid),
required onMuteCall(mute, uuid),
}) {
this.onCallAccepted = onCallAccepted;
this.onCallEnded = onCallEnded;
this.onMuteCall = onMuteCall;
ConnectycubeFlutterCallKit.instance.init(
onCallAccepted: _onCallAccepted,
onCallRejected: _onCallRejected,
icon: Platform.isAndroid ? 'default_avatar' : 'CallkitIcon',
notificationIcon: 'ic_notification',
color: '#07711e',
ringtone:
Platform.isAndroid ? 'custom_ringtone' : 'custom_ringtone.caf');
if (Platform.isIOS) {
ConnectycubeFlutterCallKit.onCallMuted = _onCallMuted;
}
}
Future<void> processCallFinished(String uuid) async {
if(Platform.isAndroid || Platform.isIOS) {
ConnectycubeFlutterCallKit.reportCallEnded(sessionId: uuid);
ConnectycubeFlutterCallKit.setOnLockScreenVisibility(isVisible: false);
}
}
/// Event Listener Callbacks for 'connectycube_flutter_call_kit'
///
Future<void> _onCallMuted(bool mute, String uuid) async {
onMuteCall.call(mute, uuid);
}
void muteCall(String sessionId, bool mute) {
ConnectycubeFlutterCallKit.reportCallMuted(sessionId: sessionId, muted: mute);
}
Future<void> _onCallAccepted(CallEvent callEvent) async {
onCallAccepted.call(callEvent.sessionId);
}
Future<void> _onCallRejected(CallEvent callEvent) async {
if (!CubeChatConnection.instance.isAuthenticated()) {
rejectCall(
callEvent.sessionId, {...callEvent.opponentsIds, callEvent.callerId});
}
onCallEnded.call(callEvent.sessionId);
}
}
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:universal_io/io.dart';
import 'package:connectycube_flutter_call_kit/connectycube_flutter_call_kit.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'call_kit_manager.dart';
import '../conversation_screen.dart';
import '../incoming_call_screen.dart';
import '../utils/configs.dart';
import '../utils/consts.dart';
class CallManager {
static String TAG = "CallManager";
static CallManager get instance => _getInstance();
static CallManager? _instance;
static CallManager _getInstance() {
return _instance ??= CallManager._internal();
}
factory CallManager() => _getInstance();
CallManager._internal();
P2PClient? _callClient;
P2PSession? _currentCall;
BuildContext? context;
MediaStream? localMediaStream;
Map<int, MediaStream> remoteStreams = {};
Function(bool, String)? onMicMuted;
init(BuildContext context) {
this.context = context;
_initCustomMediaConfigs();
if (CubeChatConnection.instance.isAuthenticated()) {
_initCalls();
} else {
_initChatConnectionStateListener();
}
_initCallKit();
}
destroy() {
_callClient?.destroy();
_callClient = null;
}
void _initCustomMediaConfigs() {
RTCMediaConfig mediaConfig = RTCMediaConfig.instance;
mediaConfig.minHeight = 340;
mediaConfig.minWidth = 480;
mediaConfig.minFrameRate = 25;
RTCConfig.instance.statsReportsInterval = 200;
}
void _initCalls() {
if (_callClient == null) {
_callClient = P2PClient.instance;
_callClient!.init();
}
_callClient!.onReceiveNewSession = (callSession) async {
if (_currentCall != null &&
_currentCall!.sessionId != callSession.sessionId) {
callSession.reject();
return;
}
_currentCall = callSession;
var callState = await _getCallState(_currentCall!.sessionId);
if (callState == CallState.REJECTED) {
reject(_currentCall!.sessionId, false);
} else if (callState == CallState.ACCEPTED) {
acceptCall(_currentCall!.sessionId, false);
} else if (callState == CallState.UNKNOWN ||
callState == CallState.PENDING) {
if (callState == CallState.UNKNOWN &&
(Platform.isIOS || Platform.isAndroid)) {
ConnectycubeFlutterCallKit.setCallState(
sessionId: _currentCall!.sessionId, callState: CallState.PENDING);
}
_showIncomingCallScreen(_currentCall!);
}
_currentCall?.onLocalStreamReceived = (localStream) {
localMediaStream = localStream;
};
_currentCall?.onRemoteStreamReceived = (session, userId, stream) {
remoteStreams[userId] = stream;
};
_currentCall?.onRemoteStreamRemoved = (session, userId, stream) {
remoteStreams.remove(userId);
};
};
_callClient!.onSessionClosed = (callSession) async {
if (_currentCall != null &&
_currentCall!.sessionId == callSession.sessionId) {
_currentCall = null;
localMediaStream?.getTracks().forEach((track) async {
await track.stop();
});
await localMediaStream?.dispose();
localMediaStream = null;
remoteStreams.forEach((key, value) async {
await value.dispose();
});
remoteStreams.clear();
CallKitManager.instance.processCallFinished(callSession.sessionId);
}
};
}
void startNewCall(
BuildContext context, int callType, Set<int> opponents) async {
if (opponents.isEmpty) return;
Helper.setAppleAudioIOMode(AppleAudioIOMode.localAndRemote);
P2PSession callSession =
_callClient!.createCallSession(callType, opponents);
_currentCall = callSession;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationCallScreen(callSession, false),
),
);
_sendStartCallSignalForOffliners(_currentCall!);
}
void _showIncomingCallScreen(P2PSession callSession) {
if (context != null) {
Navigator.push(
context!,
MaterialPageRoute(
builder: (context) => IncomingCallScreen(callSession),
),
);
}
}
void acceptCall(String sessionId, bool fromCallkit) {
log('acceptCall, from callKit: $fromCallkit', TAG);
ConnectycubeFlutterCallKit.setOnLockScreenVisibility(isVisible: true);
if (_currentCall != null) {
if (context != null) {
if (AppLifecycleState.resumed !=
WidgetsBinding.instance.lifecycleState) {
_currentCall?.acceptCall();
}
if (!fromCallkit) {
ConnectycubeFlutterCallKit.reportCallAccepted(sessionId: sessionId);
}
Navigator.pushReplacement(
context!,
MaterialPageRoute(
builder: (context) => ConversationCallScreen(_currentCall!, true),
),
);
}
Helper.setAppleAudioIOMode(AppleAudioIOMode.localAndRemote);
}
}
void reject(String sessionId, bool fromCallkit) {
if (_currentCall != null) {
if (fromCallkit) {
ConnectycubeFlutterCallKit.setOnLockScreenVisibility(isVisible: false);
} else {
CallKitManager.instance.processCallFinished(_currentCall!.sessionId);
}
_currentCall!.reject();
_sendEndCallSignalForOffliners(_currentCall);
}
}
void hungUp() {
if (_currentCall != null) {
CallKitManager.instance.processCallFinished(_currentCall!.sessionId);
_currentCall!.hungUp();
_sendEndCallSignalForOffliners(_currentCall);
}
}
CreateEventParams _getCallEventParameters(P2PSession currentCall) {
String? callerName = users
.where((cubeUser) => cubeUser.id == currentCall.callerId)
.first
.fullName;
CreateEventParams params = CreateEventParams();
params.parameters = {
'message':
"Incoming ${currentCall.callType == CallType.VIDEO_CALL ? "Video" : "Audio"} call",
PARAM_CALL_TYPE: currentCall.callType,
PARAM_SESSION_ID: currentCall.sessionId,
PARAM_CALLER_ID: currentCall.callerId,
PARAM_CALLER_NAME: callerName,
PARAM_CALL_OPPONENTS: currentCall.opponentsIds.join(','),
};
params.notificationType = NotificationType.PUSH;
params.environment =
kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
params.usersIds = currentCall.opponentsIds.toList();
return params;
}
void _sendStartCallSignalForOffliners(P2PSession currentCall) {
CreateEventParams params = _getCallEventParameters(currentCall);
params.parameters[PARAM_SIGNAL_TYPE] = SIGNAL_TYPE_START_CALL;
params.parameters[PARAM_IOS_VOIP] = 1;
params.parameters[PARAM_EXPIRATION] = 0;
params.parameters['ios_push_type'] = 'voip';
createEvent(params.getEventForRequest()).then((cubeEvent) {
log("Event for offliners created: $cubeEvent");
}).catchError((error) {
log("ERROR occurs during create event");
});
}
void _sendEndCallSignalForOffliners(P2PSession? currentCall) {
if (currentCall == null) return;
CubeUser? currentUser = CubeChatConnection.instance.currentUser;
if (currentUser == null || currentUser.id != currentCall.callerId) return;
CreateEventParams params = _getCallEventParameters(currentCall);
params.parameters[PARAM_SIGNAL_TYPE] = SIGNAL_TYPE_END_CALL;
createEvent(params.getEventForRequest()).then((cubeEvent) {
log("Event for offliners created");
}).catchError((error) {
log("ERROR occurs during create event");
});
}
void _initCallKit() {
CallKitManager.instance.init(
onCallAccepted: (uuid) {
acceptCall(uuid, true);
},
onCallEnded: (uuid) {
reject(uuid, true);
},
onMuteCall: (mute, uuid) {
onMicMuted?.call(mute, uuid);
},
);
}
void _initChatConnectionStateListener() {
CubeChatConnection.instance.connectionStateStream.listen((state) {
if (CubeChatConnectionState.Ready == state) {
_initCalls();
}
});
}
Future<String> _getCallState(String sessionId) async {
if (Platform.isAndroid || Platform.isIOS) {
var callState =
ConnectycubeFlutterCallKit.getCallState(sessionId: sessionId);
return callState;
}
return Future.value(CallState.UNKNOWN);
}
void muteCall(String sessionId, bool mute) {
CallKitManager.instance.muteCall(sessionId, mute);
}
}
import 'dart:convert';
import 'package:connectycube_flutter_call_kit/connectycube_flutter_call_kit.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:universal_io/io.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import '../../../main.dart';
import '../utils/consts.dart';
import '../utils/pref_util.dart';
class PushNotificationsManager {
static const TAG = "PushNotificationsManager";
static PushNotificationsManager? _instance;
PushNotificationsManager._internal();
static PushNotificationsManager _getInstance() {
return _instance ??= PushNotificationsManager._internal();
}
factory PushNotificationsManager() => _getInstance();
BuildContext? applicationContext;
static PushNotificationsManager get instance => _getInstance();
init() async {
ConnectycubeFlutterCallKit.initEventsHandler();
ConnectycubeFlutterCallKit.onTokenRefreshed = (token) {
log('[onTokenRefresh] VoIP token: $token', TAG);
subscribe(token);
};
ConnectycubeFlutterCallKit.getToken().then((token) {
log('[getToken] VoIP token: $token', TAG);
if (token != null) {
subscribe(token);
}
});
ConnectycubeFlutterCallKit.onCallRejectedWhenTerminated =
onCallRejectedWhenTerminated;
}
subscribe(String token) async {
log('[subscribe] token: $token', PushNotificationsManager.TAG);
var savedToken = await SharedPrefs.getSubscriptionToken();
if (token == savedToken) {
log('[subscribe] skip subscription for same token',
PushNotificationsManager.TAG);
return;
}
CreateSubscriptionParameters parameters = CreateSubscriptionParameters();
parameters.pushToken = token;
parameters.environment =
kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
if (Platform.isAndroid) {
parameters.channel = NotificationsChannels.GCM;
parameters.platform = CubePlatform.ANDROID;
} else if (Platform.isIOS) {
parameters.channel = NotificationsChannels.APNS_VOIP;
parameters.platform = CubePlatform.IOS;
}
var deviceInfoPlugin = DeviceInfoPlugin();
var deviceId;
if (kIsWeb) {
var webBrowserInfo = await deviceInfoPlugin.webBrowserInfo;
deviceId = base64Encode(utf8.encode(webBrowserInfo.userAgent ?? ''));
} else if (Platform.isAndroid) {
var androidInfo = await deviceInfoPlugin.androidInfo;
deviceId = androidInfo.id;
} else if (Platform.isIOS) {
var iosInfo = await deviceInfoPlugin.iosInfo;
deviceId = iosInfo.identifierForVendor;
} else if (Platform.isMacOS) {
var macOsInfo = await deviceInfoPlugin.macOsInfo;
deviceId = macOsInfo.computerName;
}
parameters.udid = deviceId;
var packageInfo = await PackageInfo.fromPlatform();
parameters.bundleIdentifier = packageInfo.packageName;
createSubscription(parameters.getRequestParameters())
.then((cubeSubscriptions) {
log('[subscribe] subscription SUCCESS', PushNotificationsManager.TAG);
SharedPrefs.saveSubscriptionToken(token);
cubeSubscriptions.forEach((subscription) {
if (subscription.device!.clientIdentificationSequence == token) {
SharedPrefs.saveSubscriptionId(subscription.id!);
}
});
}).catchError((error) {
log('[subscribe] subscription ERROR: $error',
PushNotificationsManager.TAG);
});
}
Future<void> unsubscribe() {
return SharedPrefs.getSubscriptionId().then((subscriptionId) async {
if (subscriptionId != 0) {
return deleteSubscription(subscriptionId).then((voidResult) {
SharedPrefs.saveSubscriptionId(0);
});
} else {
return Future.value();
}
}).catchError((onError) {
log('[unsubscribe] ERROR: $onError', PushNotificationsManager.TAG);
});
}
}
@pragma('vm:entry-point')
Future<void> onCallRejectedWhenTerminated(CallEvent callEvent) async {
print(
'[PushNotificationsManager][onCallRejectedWhenTerminated] callEvent: $callEvent');
var currentUser = await SharedPrefs.getUser();
initConnectycubeContextLess();
var sendOfflineReject = rejectCall(callEvent.sessionId, {
...callEvent.opponentsIds.where((userId) => currentUser!.id != userId),
callEvent.callerId
});
var sendPushAboutReject = sendPushAboutRejectFromKilledState({
PARAM_CALL_TYPE: callEvent.callType,
PARAM_SESSION_ID: callEvent.sessionId,
PARAM_CALLER_ID: callEvent.callerId,
PARAM_CALLER_NAME: callEvent.callerName,
PARAM_CALL_OPPONENTS: callEvent.opponentsIds.join(','),
}, callEvent.callerId);
return Future.wait([sendOfflineReject, sendPushAboutReject]).then((result) {
return Future.value();
});
}
Future<void> sendPushAboutRejectFromKilledState(
Map<String, dynamic> parameters,
int callerId,
) {
CreateEventParams params = CreateEventParams();
params.parameters = parameters;
params.parameters['message'] = "Reject call";
params.parameters[PARAM_SIGNAL_TYPE] = SIGNAL_TYPE_REJECT_CALL;
// params.parameters[PARAM_IOS_VOIP] = 1;
params.notificationType = NotificationType.PUSH;
params.environment =
kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
params.usersIds = [callerId];
return createEvent(params.getEventForRequest());
}
import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'login_screen.dart';
import 'managers/call_manager.dart';
import 'managers/push_notifications_manager.dart';
import 'utils/configs.dart' as utils;
import 'utils/platform_utils.dart';
import 'utils/pref_util.dart';
class SelectOpponentsScreen extends StatelessWidget {
final CubeUser currentUser;
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onBackPressed(),
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
'Logged in as ${CubeChatConnection.instance.currentUser!.fullName}',
),
actions: <Widget>[
IconButton(
onPressed: () => _logOut(context),
icon: Icon(
Icons.exit_to_app,
color: Colors.white,
),
),
],
),
body: BodyLayout(currentUser),
),
);
}
Future<bool> _onBackPressed() {
return Future.value(false);
}
_logOut(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Logout"),
content: Text("Are you sure you want logout current user"),
actions: <Widget>[
TextButton(
child: Text("CANCEL"),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text("OK"),
onPressed: () async {
CallManager.instance.destroy();
CubeChatConnection.instance.destroy();
await PushNotificationsManager.instance.unsubscribe();
await SharedPrefs.deleteUserData();
await signOut();
Navigator.pop(context); // cancel current Dialog
_navigateToLoginScreen(context);
},
),
],
);
},
);
}
_navigateToLoginScreen(BuildContext context) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
(r) => false,
);
}
SelectOpponentsScreen(this.currentUser);
}
class BodyLayout extends StatefulWidget {
final CubeUser currentUser;
@override
State<StatefulWidget> createState() {
return _BodyLayoutState(currentUser);
}
BodyLayout(this.currentUser);
}
class _BodyLayoutState extends State<BodyLayout> {
final CubeUser currentUser;
late Set<int> _selectedUsers;
_BodyLayoutState(this.currentUser);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 48, left: 48, right: 48, bottom: 12),
child: Column(
children: [
Text(
"Select users to call:",
style: TextStyle(fontSize: 22),
),
Expanded(
child: _getOpponentsList(context),
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(12),
child: FloatingActionButton(
heroTag: "VideoCall",
child: Icon(
Icons.videocam,
color: Colors.white,
),
backgroundColor: Colors.blue,
onPressed: () => CallManager.instance.startNewCall(
context, CallType.VIDEO_CALL, _selectedUsers),
),
),
Padding(
padding: EdgeInsets.all(12),
child: FloatingActionButton(
heroTag: "AudioCall",
child: Icon(
Icons.call,
color: Colors.white,
),
backgroundColor: Colors.green,
onPressed: () => CallManager.instance.startNewCall(
context, CallType.AUDIO_CALL, _selectedUsers),
),
),
],
),
],
));
}
Widget _getOpponentsList(BuildContext context) {
CubeUser? currentUser = CubeChatConnection.instance.currentUser;
final users =
utils.users.where((user) => user.id != currentUser!.id).toList();
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return Card(
child: CheckboxListTile(
title: Center(
child: Text(
users[index].fullName!,
),
),
value: _selectedUsers.contains(users[index].id),
onChanged: ((checked) {
setState(() {
if (checked!) {
_selectedUsers.add(users[index].id!);
} else {
_selectedUsers.remove(users[index].id);
}
});
}),
),
);
},
);
}
@override
void initState() {
super.initState();
initForegroundService();
_selectedUsers = {};
checkSystemAlertWindowPermission(context);
requestNotificationsPermission();
CallManager.instance.init(context);
PushNotificationsManager.instance.init();
}
}
import 'package:connectycube_sdk/connectycube_sdk.dart';
const String APP_ID = "7236";
const String AUTH_KEY = "JFPtjOKO5Y979D7";
const String AUTH_SECRET = "zvsfjsUTngTwbqB";
const String DEFAULT_PASS = "Flutter2000";
List<CubeUser> users = [
CubeUser(
id: 11253684,
login: "Flutter2000",
fullName: "Inomjon",
password: DEFAULT_PASS,
),
];
final String PARAM_SESSION_ID = 'session_id';
final String PARAM_CALL_TYPE = 'call_type';
final String PARAM_CALLER_ID = 'caller_id';
final String PARAM_CALLER_NAME = 'caller_name';
final String PARAM_CALL_OPPONENTS = 'call_opponents';
final String PARAM_IOS_VOIP = 'ios_voip';
final String PARAM_SIGNAL_TYPE = 'signal_type';
final String PARAM_EXPIRATION = 'expiration';
final String SIGNAL_TYPE_START_CALL = "startCall";
final String SIGNAL_TYPE_END_CALL = "endCall";
final String SIGNAL_TYPE_REJECT_CALL = "rejectCall";
import 'package:connectycube_sdk/connectycube_core.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background/flutter_background.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:universal_io/io.dart';
Future<bool> initForegroundService() async {
if (Platform.isAndroid) {
final androidConfig = FlutterBackgroundAndroidConfig(
notificationTitle: 'P2P Calls sample',
notificationText: 'Screen sharing is in progress',
notificationImportance: AndroidNotificationImportance.Default,
notificationIcon:
AndroidResource(name: 'ic_launcher_foreground', defType: 'drawable'),
);
return FlutterBackground.initialize(androidConfig: androidConfig);
} else {
return Future.value(true);
}
}
Future<bool> startBackgroundExecution() async {
if (Platform.isAndroid) {
return initForegroundService().then((_) {
return FlutterBackground.enableBackgroundExecution();
});
} else {
return Future.value(true);
}
}
Future<bool> stopBackgroundExecution() async {
if (Platform.isAndroid && FlutterBackground.isBackgroundExecutionEnabled) {
return FlutterBackground.disableBackgroundExecution();
} else {
return Future.value(true);
}
}
Future<bool> hasBackgroundExecutionPermissions() async {
if (Platform.isAndroid) {
return FlutterBackground.hasPermissions;
} else {
return Future.value(true);
}
}
Future<void> checkSystemAlertWindowPermission(BuildContext context) async {
if (Platform.isAndroid) {
var androidInfo = await DeviceInfoPlugin().androidInfo;
var sdkInt = androidInfo.version.sdkInt!;
if (sdkInt >= 31) {
if (await Permission.systemAlertWindow.isDenied) {
showDialog(
context: context,
builder: (BuildContext context) {
return Expanded(
child: AlertDialog(
title: Text('Permission required'),
content: Text(
'For accepting the calls in the background you should provide access to show System Alerts from the background. Would you like to do it now?'),
actions: [
TextButton(
onPressed: () {
Permission.systemAlertWindow.request().then((status) {
if (status.isGranted) {
Navigator.of(context).pop();
}
});
},
child: Text(
'Allow',
),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Later',
),
),
],
),
);
},
);
}
}
}
}
requestNotificationsPermission() async {
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isWindows)) {
var isPermissionGranted = await Permission.notification.isGranted;
log('isPermissionGranted = $isPermissionGranted', 'platform_utils');
if (!isPermissionGranted) {
await Permission.notification.request();
}
}
}
import 'dart:async';
import 'package:connectycube_sdk/connectycube_chat.dart';
import 'package:shared_preferences/shared_preferences.dart';
const String prefUserLogin = "pref_user_login";
const String prefUserPsw = "pref_user_psw";
const String prefUserName = "pref_user_name";
const String prefUserId = "pref_user_id";
const String prefUserAvatar = "pref_user_avatar";
const String prefSubscriptionToken = "pref_subscription_token";
const String prefSubscriptionId = "pref_subscription_id";
class SharedPrefs {
static SharedPreferences? _prefs;
static Future<SharedPreferences> getPrefs() async {
Completer<SharedPreferences> completer = Completer();
if (_prefs != null) {
completer.complete(_prefs);
} else {
_prefs = await SharedPreferences.getInstance();
completer.complete(_prefs);
}
return completer.future;
}
static Future<bool> saveNewUser(CubeUser cubeUser) {
return getPrefs().then((prefs) {
prefs.clear();
prefs.setString(prefUserLogin, cubeUser.login!);
prefs.setString(prefUserPsw, cubeUser.password!);
prefs.setString(prefUserName, cubeUser.fullName!);
prefs.setInt(prefUserId, cubeUser.id!);
if (cubeUser.avatar != null)
prefs.setString(prefUserAvatar, cubeUser.avatar!);
return Future.value(true);
});
}
static Future<bool> updateUser(CubeUser cubeUser) {
return getPrefs().then((prefs) {
if (cubeUser.password != null)
prefs.setString(prefUserPsw, cubeUser.password!);
if (cubeUser.login != null)
prefs.setString(prefUserLogin, cubeUser.login!);
if (cubeUser.fullName != null)
prefs.setString(prefUserName, cubeUser.fullName!);
if (cubeUser.avatar != null)
prefs.setString(prefUserAvatar, cubeUser.avatar!);
return Future.value(true);
});
}
static Future<CubeUser?> getUser() {
return getPrefs().then((prefs) {
if (prefs.getString(prefUserLogin) == null) return Future.value();
var user = CubeUser();
user.login = prefs.getString(prefUserLogin);
user.password = prefs.getString(prefUserPsw);
user.fullName = prefs.getString(prefUserName);
user.id = prefs.getInt(prefUserId);
user.avatar = prefs.getString(prefUserAvatar);
return Future.value(user);
});
}
static Future<bool> deleteUserData() {
return getPrefs().then((prefs) {
return prefs.clear();
});
}
static Future<bool> saveSubscriptionToken(String token) {
return getPrefs().then((prefs) {
return prefs.setString(prefSubscriptionToken, token);
});
}
static Future<String> getSubscriptionToken() {
return getPrefs().then((prefs) {
return Future.value(prefs.getString(prefSubscriptionToken) ?? "");
});
}
static Future<bool> saveSubscriptionId(int id) {
return getPrefs().then((prefs) {
return prefs.setInt(prefSubscriptionId, id);
});
}
static Future<int> getSubscriptionId() {
return getPrefs().then((prefs) {
return Future.value(prefs.getInt(prefSubscriptionId) ?? 0);
});
}
}
......@@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
}
......@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_webrtc
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
......
......@@ -5,6 +5,18 @@
import FlutterMacOS
import Foundation
import device_info_plus
import firebase_core
import flutter_webrtc
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyCF92QjQTaDqzw4iVhvSmNEEc9aJSiRa-0</string>
<key>GCM_SENDER_ID</key>
<string>130671191292</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>uz.technounitgroup.vmeeting.RunnerTests</string>
<key>PROJECT_ID</key>
<string>connectycubevideocall</string>
<key>STORAGE_BUCKET</key>
<string>connectycubevideocall.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:130671191292:ios:608d88189b2e4fd64ba379</string>
</dict>
</plist>
\ No newline at end of file
{
"file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:130671191292:ios:608d88189b2e4fd64ba379",
"FIREBASE_PROJECT_ID": "connectycubevideocall",
"GCM_SENDER_ID": "130671191292"
}
\ No newline at end of file
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03"
url: "https://pub.dev"
source: hosted
version: "3.4.6"
async:
dependency: transitive
description:
......@@ -25,6 +33,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
url: "https://pub.dev"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
......@@ -41,6 +57,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.17.2"
connectycube_flutter_call_kit:
dependency: "direct main"
description:
name: connectycube_flutter_call_kit
sha256: "1d74e4986902e7bf6badc5d1f47bf81588c1760fa04ad2d0eae7c650f4678ae8"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
connectycube_sdk:
dependency: "direct main"
description:
name: connectycube_sdk
sha256: fe1a6d5423eb02459c1469ec95cf6eac90b63aa3c51add86691f8a5b6ff8e601
url: "https://pub.dev"
source: hosted
version: "2.10.0"
console:
dependency: transitive
description:
name: console
sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a
url: "https://pub.dev"
source: hosted
version: "4.1.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cryptoutils:
dependency: transitive
description:
name: cryptoutils
sha256: e7475f6e5e58f94b975dd537b30ee3507bf6b2370e1b868f381f928d3d4d524c
url: "https://pub.dev"
source: hosted
version: "0.5.0"
csslib:
dependency: transitive
description:
name: csslib
sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
cupertino_icons:
dependency: "direct main"
description:
......@@ -49,6 +121,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.6"
dart_webrtc:
dependency: transitive
description:
name: dart_webrtc
sha256: "5897a3bdd6c7fded07e80e250260ca4c9cd61f9080911aa308b516e1206745a9"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
url: "https://pub.dev"
source: hosted
version: "7.0.0"
fake_async:
dependency: transitive
description:
......@@ -57,11 +153,59 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "37299e4907391d7fac8c7ea059bb3292768cc07b72b6c6c777675cc58da2ef4d"
url: "https://pub.dev"
source: hosted
version: "2.20.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63
url: "https://pub.dev"
source: hosted
version: "5.0.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "0631a2ec971dbc540275e2fa00c3a8a2676f0a7adbc3c197d6fba569db689d97"
url: "https://pub.dev"
source: hosted
version: "2.8.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_background:
dependency: "direct main"
description:
name: flutter_background
sha256: "035c31a738509d67ee70bbf174e5aa7db462c371e838ec8259700c5c4e7ca17f"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
flutter_lints:
dependency: "direct dev"
description:
......@@ -70,11 +214,80 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
flutter_speed_dial:
dependency: "direct main"
description:
name: flutter_speed_dial
sha256: "41d7ad0bc224248637b3a5e0b9083e912a75445bdb450cf82b8ed06d7af7c61d"
url: "https://pub.dev"
source: hosted
version: "6.2.0"
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"
flutter_webrtc:
dependency: transitive
description:
name: flutter_webrtc
sha256: da6fb0f0f9515b2c64608011e3ad314e6a858bbf1c57477f418621996e7d22d2
url: "https://pub.dev"
source: hosted
version: "0.9.45"
html:
dependency: transitive
description:
name: html
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
url: "https://pub.dev"
source: hosted
version: "0.15.4"
http:
dependency: transitive
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271"
url: "https://pub.dev"
source: hosted
version: "4.1.3"
intl:
dependency: transitive
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
lints:
dependency: transitive
description:
......@@ -107,6 +320,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
version: "1.0.4"
objectid:
dependency: transitive
description:
name: objectid
sha256: "22fa972000d3256f10d06323a9dcbf4b564fb03fdb9024399e3a6c1d9902f914"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
path:
dependency: transitive
description:
......@@ -115,6 +360,206 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.3"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
url: "https://pub.dev"
source: hosted
version: "10.4.5"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
url: "https://pub.dev"
source: hosted
version: "10.3.6"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
url: "https://pub.dev"
source: hosted
version: "9.1.4"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
url: "https://pub.dev"
source: hosted
version: "3.12.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
url: "https://pub.dev"
source: hosted
version: "0.1.3"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: "https://pub.dev"
source: hosted
version: "5.4.0"
platform:
dependency: transitive
description:
name: platform
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
platform_detect:
dependency: transitive
description:
name: platform_detect
sha256: "08f4ee79c0e1c4858d37e06b22352a3ebdef5466b613749a3adb03e703d4f5b0"
url: "https://pub.dev"
source: hosted
version: "2.0.11"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
url: "https://pub.dev"
source: hosted
version: "2.1.6"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
quiver:
dependency: transitive
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
url: "https://pub.dev"
source: hosted
version: "3.2.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
url: "https://pub.dev"
source: hosted
version: "2.3.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
url: "https://pub.dev"
source: hosted
version: "2.3.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf
url: "https://pub.dev"
source: hosted
version: "2.2.1"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
sky_engine:
dependency: transitive
description: flutter
......@@ -152,6 +597,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
term_glyph:
dependency: transitive
description:
......@@ -168,6 +621,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
universal_html:
dependency: transitive
description:
name: universal_html
sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
universal_io:
dependency: "direct main"
description:
name: universal_io
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
unorm_dart:
dependency: transitive
description:
name: unorm_dart
sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
url: "https://pub.dev"
source: hosted
version: "3.0.7"
vector_math:
dependency: transitive
description:
......@@ -184,5 +685,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_browser_detect:
dependency: "direct main"
description:
name: web_browser_detect
sha256: "78ba66860b61a993030788a3a4586fb21cb7d9cef966cfd24faa9ad487c3fd8b"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.4.0"
webrtc_interface:
dependency: transitive
description:
name: webrtc_interface
sha256: "2efbd3e4e5ebeb2914253bcc51dafd3053c4b87b43f3076c74835a9deecbae3a"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
win32:
dependency: transitive
description:
name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
url: "https://pub.dev"
source: hosted
version: "5.0.9"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
xml:
dependency: transitive
description:
name: xml
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
xmpp_stone:
dependency: transitive
description:
name: xmpp_stone
sha256: c1ea5b898ef36b35f496556fd8a527c382c200a50c20bebc25c9742f1050ab25
url: "https://pub.dev"
source: hosted
version: "0.4.4-dev.4"
sdks:
dart: ">=3.1.3 <4.0.0"
flutter: ">=3.7.0"
......@@ -35,6 +35,17 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
connectycube_sdk: ^2.10.0
shared_preferences: ^2.2.2
connectycube_flutter_call_kit: ^2.3.0
universal_io: ^2.2.2
web_browser_detect: ^2.0.3
flutter_background: ^1.2.0
flutter_speed_dial: ^6.2.0
permission_handler: ^10.0.1
device_info_plus: ^9.0.0
package_info_plus: ^4.0.0
firebase_core: ^2.20.0
dev_dependencies:
flutter_test:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment