diff --git a/.gitignore b/.gitignore index 496ee2c..00741cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.DS_Store \ No newline at end of file +.DS_Store +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index 6d98bb8..de3f575 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,6 @@ ## [Audio call](audio_call) The demo demonstrates basic audio call functionality of the Voximplant Flutter SDK. + +## [Video call](video_call) +The demo demonstrates basic video call functionality of the Voximplant Flutter SDK. diff --git a/audio_call/.gitignore b/audio_call/.gitignore index e0c65fc..8f72d2d 100644 --- a/audio_call/.gitignore +++ b/audio_call/.gitignore @@ -24,6 +24,7 @@ **/doc/api/ .dart_tool/ .flutter-plugins +.flutter-plugins-dependencies .packages .pub-cache/ .pub/ @@ -37,6 +38,7 @@ **/android/gradlew.bat **/android/local.properties **/android/**/GeneratedPluginRegistrant.java +**/android/gradle/ # iOS/XCode related **/ios/**/*.mode1v3 @@ -66,6 +68,8 @@ **/ios/Flutter/flutter_export_environment.sh **/ios/Runner.xcworkspace/xcshareddata/** +**/ios/Flutter/Flutter.podspec + # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 diff --git a/audio_call/android/app/src/main/AndroidManifest.xml b/audio_call/android/app/src/main/AndroidManifest.xml index e58fba2..a74e963 100644 --- a/audio_call/android/app/src/main/AndroidManifest.xml +++ b/audio_call/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,6 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> - - + + diff --git a/audio_call/android/app/src/main/java/com/voximplant/flutter/audio_call/App.java b/audio_call/android/app/src/main/java/com/voximplant/flutter/audio_call/App.java deleted file mode 100644 index f7f9b1f..0000000 --- a/audio_call/android/app/src/main/java/com/voximplant/flutter/audio_call/App.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.voximplant.flutter.audio_call; - -import io.flutter.app.FlutterApplication; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugins.GeneratedPluginRegistrant; -import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService; - -public class App extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback { - @Override - public void onCreate() { - super.onCreate(); - FlutterFirebaseMessagingService.setPluginRegistrant(this); - } - - @Override - public void registerWith(PluginRegistry pluginRegistry) { - GeneratedPluginRegistrant.registerWith(pluginRegistry); - } -} diff --git a/audio_call/android/app/src/main/java/com/voximplant/flutter/audio_call/MainActivity.java b/audio_call/android/app/src/main/java/com/voximplant/flutter/audio_call/MainActivity.java index 22674ae..856a4a7 100644 --- a/audio_call/android/app/src/main/java/com/voximplant/flutter/audio_call/MainActivity.java +++ b/audio_call/android/app/src/main/java/com/voximplant/flutter/audio_call/MainActivity.java @@ -1,13 +1,13 @@ package com.voximplant.flutter.audio_call; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin; public class MainActivity extends FlutterActivity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new FirebaseMessagingPlugin()); } } diff --git a/audio_call/android/build.gradle b/audio_call/android/build.gradle index 305f967..846858b 100644 --- a/audio_call/android/build.gradle +++ b/audio_call/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.5.0' classpath 'com.google.gms:google-services:4.3.3' } } diff --git a/audio_call/android/gradle.properties b/audio_call/android/gradle.properties index 1441b1d..38c8d45 100644 --- a/audio_call/android/gradle.properties +++ b/audio_call/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx1536M - android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/audio_call/android/gradle/wrapper/gradle-wrapper.properties b/audio_call/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 2819f02..0000000 --- a/audio_call/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/audio_call/ios/Podfile b/audio_call/ios/Podfile index 649f0e2..c28fd4e 100644 --- a/audio_call/ios/Podfile +++ b/audio_call/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project - platform :ios, '9.0' +platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -15,49 +15,64 @@ def parse_KV_file(file, separator='=') if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) { |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - pods_ary.push({:name => podname, :path => podpath}); - else - puts "Invalid plugin specification: #{line}" - end - } - return pods_ary + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do - # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock - # referring to absolute paths on developers' machines. - system('rm -rf .symlinks') - system('mkdir -p .symlinks/plugins') + # Flutter Pod - # Flutter Pods - generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') - if generated_xcode_build_settings.empty? - puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." - end - generated_xcode_build_settings.map { |p| - if p[:name] == 'FLUTTER_FRAMEWORK_DIR' - symlink = File.join('.symlinks', 'flutter') - File.symlink(File.dirname(p[:path]), symlink) - pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" end - } + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.map { |p| - symlink = File.join('.symlinks', 'plugins', p[:name]) - File.symlink(p[:path], symlink) - pod p[:name], :path => File.join(symlink, 'ios') - } + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. diff --git a/audio_call/ios/Podfile.lock b/audio_call/ios/Podfile.lock index 900ba77..e0a7b7c 100644 --- a/audio_call/ios/Podfile.lock +++ b/audio_call/ios/Podfile.lock @@ -1,50 +1,55 @@ PODS: - - Firebase/Core (6.13.0): + - Firebase/Core (6.15.0): - Firebase/CoreOnly - - FirebaseAnalytics (= 6.1.6) - - Firebase/CoreOnly (6.13.0): - - FirebaseCore (= 6.4.0) - - Firebase/Messaging (6.13.0): + - FirebaseAnalytics (= 6.2.1) + - Firebase/CoreOnly (6.15.0): + - FirebaseCore (= 6.6.0) + - Firebase/Messaging (6.15.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 4.1.9) + - FirebaseMessaging (~> 4.2.0) - firebase_messaging (0.0.1): - Firebase/Core - Firebase/Messaging - Flutter - - FirebaseAnalytics (6.1.6): - - FirebaseCore (~> 6.4) - - FirebaseInstanceID (~> 4.2) - - GoogleAppMeasurement (= 6.1.6) + - FirebaseAnalytics (6.2.1): + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - GoogleAppMeasurement (= 6.2.1) - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - GoogleUtilities/MethodSwizzler (~> 6.0) - GoogleUtilities/Network (~> 6.0) - "GoogleUtilities/NSData+zlib (~> 6.0)" - nanopb (= 0.3.9011) - - FirebaseAnalyticsInterop (1.4.0) - - FirebaseCore (6.4.0): - - FirebaseCoreDiagnostics (~> 1.0) - - FirebaseCoreDiagnosticsInterop (~> 1.0) - - GoogleUtilities/Environment (~> 6.2) - - GoogleUtilities/Logger (~> 6.2) - - FirebaseCoreDiagnostics (1.1.2): - - FirebaseCoreDiagnosticsInterop (~> 1.0) - - GoogleDataTransportCCTSupport (~> 1.0) - - GoogleUtilities/Environment (~> 6.2) - - GoogleUtilities/Logger (~> 6.2) + - FirebaseAnalyticsInterop (1.5.0) + - FirebaseCore (6.6.0): + - FirebaseCoreDiagnostics (~> 1.2) + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) + - FirebaseCoreDiagnostics (1.2.0): + - FirebaseCoreDiagnosticsInterop (~> 1.2) + - GoogleDataTransportCCTSupport (~> 1.3) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Logger (~> 6.5) - nanopb (~> 0.3.901) - - FirebaseCoreDiagnosticsInterop (1.1.0) - - FirebaseInstanceID (4.2.7): - - FirebaseCore (~> 6.0) - - GoogleUtilities/Environment (~> 6.0) - - GoogleUtilities/UserDefaults (~> 6.0) - - FirebaseMessaging (4.1.9): - - FirebaseAnalyticsInterop (~> 1.3) - - FirebaseCore (~> 6.2) - - FirebaseInstanceID (~> 4.1) - - GoogleUtilities/AppDelegateSwizzler (~> 6.2) - - GoogleUtilities/Environment (~> 6.2) - - GoogleUtilities/Reachability (~> 6.2) - - GoogleUtilities/UserDefaults (~> 6.2) + - FirebaseCoreDiagnosticsInterop (1.2.0) + - FirebaseInstallations (1.1.0): + - FirebaseCore (~> 6.6) + - GoogleUtilities/UserDefaults (~> 6.5) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (4.3.0): + - FirebaseCore (~> 6.6) + - FirebaseInstallations (~> 1.0) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) + - FirebaseMessaging (4.2.0): + - FirebaseAnalyticsInterop (~> 1.5) + - FirebaseCore (~> 6.6) + - FirebaseInstanceID (~> 4.3) + - GoogleUtilities/AppDelegateSwizzler (~> 6.5) + - GoogleUtilities/Environment (~> 6.5) + - GoogleUtilities/Reachability (~> 6.5) + - GoogleUtilities/UserDefaults (~> 6.5) - Protobuf (>= 3.9.2, ~> 3.9) - Flutter (1.0.0) - flutter_call_kit (0.0.1): @@ -53,36 +58,36 @@ PODS: - Flutter - flutter_voip_push_notification (0.0.1): - Flutter - - flutter_voximplant (1.2.0): + - flutter_voximplant (2.0.0): - Flutter - - VoxImplantSDK (= 2.25.2) - - GoogleAppMeasurement (6.1.6): + - VoxImplantSDK (= 2.26.0) + - GoogleAppMeasurement (6.2.1): - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - GoogleUtilities/MethodSwizzler (~> 6.0) - GoogleUtilities/Network (~> 6.0) - "GoogleUtilities/NSData+zlib (~> 6.0)" - nanopb (= 0.3.9011) - - GoogleDataTransport (3.2.0) - - GoogleDataTransportCCTSupport (1.2.2): - - GoogleDataTransport (~> 3.2) + - GoogleDataTransport (3.3.0) + - GoogleDataTransportCCTSupport (1.3.0): + - GoogleDataTransport (~> 3.3) - nanopb (~> 0.3.901) - - GoogleUtilities/AppDelegateSwizzler (6.3.2): + - GoogleUtilities/AppDelegateSwizzler (6.5.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (6.3.2) - - GoogleUtilities/Logger (6.3.2): + - GoogleUtilities/Environment (6.5.0) + - GoogleUtilities/Logger (6.5.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (6.3.2): + - GoogleUtilities/MethodSwizzler (6.5.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (6.3.2): + - GoogleUtilities/Network (6.5.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (6.3.2)" - - GoogleUtilities/Reachability (6.3.2): + - "GoogleUtilities/NSData+zlib (6.5.0)" + - GoogleUtilities/Reachability (6.5.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (6.3.2): + - GoogleUtilities/UserDefaults (6.5.0): - GoogleUtilities/Logger - nanopb (0.3.9011): - nanopb/decode (= 0.3.9011) @@ -91,18 +96,19 @@ PODS: - nanopb/encode (0.3.9011) - permission_handler (3.3.0): - Flutter - - Protobuf (3.11.1) + - PromisesObjC (1.2.8) + - Protobuf (3.11.2) - shared_preferences (0.0.1): - Flutter - - VoxImplantSDK (2.25.2): - - VoxImplantSDK/Core (= 2.25.2) - - VoxImplantSDK/Core (2.25.2): - - VoxImplantWebRTC (= 74.1.0) - - VoxImplantWebRTC (74.1.0) + - VoxImplantSDK (2.26.0): + - VoxImplantSDK/Core (= 2.26.0) + - VoxImplantSDK/Core (2.26.0): + - VoxImplantWebRTC (= 78.0.0) + - VoxImplantWebRTC (78.0.0) DEPENDENCIES: - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - - Flutter (from `.symlinks/flutter/ios`) + - Flutter (from `Flutter`) - flutter_call_kit (from `.symlinks/plugins/flutter_call_kit/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_voip_push_notification (from `.symlinks/plugins/flutter_voip_push_notification/ios`) @@ -118,6 +124,7 @@ SPEC REPOS: - FirebaseCore - FirebaseCoreDiagnostics - FirebaseCoreDiagnosticsInterop + - FirebaseInstallations - FirebaseInstanceID - FirebaseMessaging - GoogleAppMeasurement @@ -125,6 +132,7 @@ SPEC REPOS: - GoogleDataTransportCCTSupport - GoogleUtilities - nanopb + - PromisesObjC - Protobuf - VoxImplantSDK - VoxImplantWebRTC @@ -133,7 +141,7 @@ EXTERNAL SOURCES: firebase_messaging: :path: ".symlinks/plugins/firebase_messaging/ios" Flutter: - :path: ".symlinks/flutter/ios" + :path: Flutter flutter_call_kit: :path: ".symlinks/plugins/flutter_call_kit/ios" flutter_local_notifications: @@ -148,31 +156,33 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences/ios" SPEC CHECKSUMS: - Firebase: 458d109512200d1aca2e1b9b6cf7d68a869a4a46 - firebase_messaging: db3f19ec4490799e5c660723626740e539c41c04 - FirebaseAnalytics: 45f36d9c429fc91d206283900ab75390cd05ee8a - FirebaseAnalyticsInterop: d48b6ab67bcf016a05e55b71fc39c61c0cb6b7f3 - FirebaseCore: 307ea2508df730c5865334e41965bd9ea344b0e5 - FirebaseCoreDiagnostics: 511f4f3ed7d440bb69127e8b97c2bc8befae639e - FirebaseCoreDiagnosticsInterop: e9b1b023157e3a2fc6418b5cb601e79b9af7b3a0 - FirebaseInstanceID: ebd2ea79ee38db0cb5f5167b17a0d387e1cc7b6e - FirebaseMessaging: e8d71368a5c579083da02203146c953f3386d503 + Firebase: 5d77105d9740a07ca6b16927ca971db7e860faaf + firebase_messaging: 73b3e7dd7b3b6a7e4bdac10d5295211ca4f87f90 + FirebaseAnalytics: e83e64b1231dedcd9ddd4bdecd9bcfd6ba341679 + FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae + FirebaseCore: 4aeb81ff53dcd9a3634ca725dc1fb8c2a4622046 + FirebaseCoreDiagnostics: 5e78803ab276bc5b50340e3c539c06c3de35c649 + FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 + FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b + FirebaseInstanceID: 6668efc1655a4052c083f287a7141f1ead12f9c2 + FirebaseMessaging: 448cf4c727f3c95f4f90816907d67306ba785cfe Flutter: 0e3d915762c693b495b44d77113d4970485de6ec flutter_call_kit: 0598f2eec54579aa00b53fb68a622ccdb08fe4fe flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 flutter_voip_push_notification: f28f3ce1c6b835a3fa9e833f37f33dc1291ab888 - flutter_voximplant: 45b9dabfd7f73e21cb5ef3dd28a4afab39853f4d - GoogleAppMeasurement: dfe55efa543e899d906309eaaac6ca26d249862f - GoogleDataTransport: 8e9b210c97d55fbff306cc5468ff91b9cb32dcf5 - GoogleDataTransportCCTSupport: ef79a4728b864946a8aafdbab770d5294faf3b5f - GoogleUtilities: 547a86735c6f0ee30ad17e94df4fc21f616b71cb + flutter_voximplant: 5d2223da5d47ff2ee5f43c84a60c8fc4f5a01ffa + GoogleAppMeasurement: a08a43b8677b95ed51fcef880e36737334d804fd + GoogleDataTransport: 574a983e829327d7c18f2627f65d9e80164ea8a4 + GoogleDataTransportCCTSupport: cad3cd6cdbdbad6b5c2c9206ec413402755faaaa + GoogleUtilities: f8de7ddf8c706f58e9b405d53e38bbdaa2731e5a nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd permission_handler: 67637977b227d62d46bfbf524f335f8568de5a73 - Protobuf: 20d79da7f20b5928b80043b05080b816e802659e + PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 + Protobuf: dd1aaea7140debfe4dd0683fb8ef208e527ae153 shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523 - VoxImplantSDK: 003dcf5b3413cec415a03aaaf7986ad86c148c82 - VoxImplantWebRTC: f1bfe15b2a7d3fd8875d204c1b3dbb370279b12e + VoxImplantSDK: c53bd6ed2634a43a33ae0d517cc90c7ae4b0bed4 + VoxImplantWebRTC: a4bd73d41b9f8f3a60de032337b661bab8ead6f9 -PODFILE CHECKSUM: 091f1b57aaebffcf04ec409c33ea6801c9622bcc +PODFILE CHECKSUM: 49ec7d4076524b7e225c38b98147173651ac4b9d -COCOAPODS: 1.8.4 +COCOAPODS: 1.8.3 diff --git a/audio_call/lib/main.dart b/audio_call/lib/main.dart index 2008f3d..b366706 100644 --- a/audio_call/lib/main.dart +++ b/audio_call/lib/main.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'dart:convert'; import 'dart:io'; diff --git a/audio_call/lib/screens/call_screen.dart b/audio_call/lib/screens/call_screen.dart index db4d4a2..b74999d 100644 --- a/audio_call/lib/screens/call_screen.dart +++ b/audio_call/lib/screens/call_screen.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'package:audio_call/screens/main_screen.dart'; import 'package:audio_call/services/call_service.dart'; @@ -19,13 +19,13 @@ class CallScreen extends StatefulWidget { } class CallScreenState extends State { - String _endpointName; + String _endpointName = 'Unknown'; String _callStatus = 'Connecting...'; bool _isAudioMuted = false; bool _isOnHold = false; String _callId; IconData _audioDeviceIcon = Icons.hearing; - AudioDeviceManager _audioDeviceManager; + VIAudioDeviceManager _audioDeviceManager; final CallService _callService = CallService(); CallScreenState(this._callId) { @@ -38,47 +38,46 @@ class CallScreenState extends State { onCallRinging: _onCallRinging, onCallAudioStarted: _onCallAudioStarted, onEndpointAdded: _onEndpointAdded, - onCallMuted: _onCallMuted, onCallPutOnHold: _onCallPutOnHold, ); - _endpointName = _callService.getEndpointNameForCall(_callId) ?? 'Unknown'; print('CallScreen: received callId: $_callId'); } - _onAudioDeviceChange(AudioDevice audioDevice) { + _onAudioDeviceChange( + VIAudioDeviceManager audioDeviceManager, VIAudioDevice audioDevice) { setState(() { switch (audioDevice) { - case AudioDevice.Bluetooth: + case VIAudioDevice.Bluetooth: _audioDeviceIcon = Icons.bluetooth_audio; break; - case AudioDevice.Earpiece: + case VIAudioDevice.Earpiece: _audioDeviceIcon = Icons.hearing; break; - case AudioDevice.Speaker: + case VIAudioDevice.Speaker: _audioDeviceIcon = Icons.volume_up; break; - case AudioDevice.WiredHeadset: + case VIAudioDevice.WiredHeadset: _audioDeviceIcon = Icons.headset; break; - case AudioDevice.None: + case VIAudioDevice.None: break; } }); } - _onCallDisconnected(Map headers, bool answeredElsewhere) { + _onCallDisconnected(VICall call, Map headers, bool answeredElsewhere) { print('CallScreen: onCallDisconnected'); Navigator.pushReplacementNamed(context, MainScreen.routeName); } - _onCallFailed(int code, String description, Map headers) { + _onCallFailed(VICall call, int code, String description, Map headers) { print('CallScreen: onCallFailed'); Navigator.pushReplacementNamed(context, MainScreen.routeName); } - _onCallConnected(Map headers) { + _onCallConnected(VICall call, Map headers) { print('CallScreen: onCallConnected'); setState(() { _callStatus = 'Call in progress'; @@ -86,18 +85,18 @@ class CallScreenState extends State { }); } - _onCallRinging(Map headers) { + _onCallRinging(VICall call, Map headers) { print('CallScreen: onCallRinging'); setState(() { _callStatus = 'Ringing...'; }); } - _onCallAudioStarted() { + _onCallAudioStarted(VICall call) { print('CallScreen: onCallAudioStarted'); } - _onEndpointAdded(Endpoint endpoint) { + _onEndpointAdded(VICall call, VIEndpoint endpoint) { print('CallScreen: onEndpointAdded'); endpoint.onEndpointUpdated = _onEndpointUpdated; setState(() { @@ -105,7 +104,7 @@ class CallScreenState extends State { }); } - _onEndpointUpdated(Endpoint endpoint) { + _onEndpointUpdated(VIEndpoint endpoint) { print('CallScreen: onEndpointUpdated'); setState(() { _endpointName = endpoint.userName; @@ -124,8 +123,7 @@ class CallScreenState extends State { setState(() { _isAudioMuted = !_isAudioMuted; }); - } catch (e) { - } + } catch (e) {} } _onCallPutOnHold(bool onHold) { @@ -140,16 +138,15 @@ class CallScreenState extends State { setState(() { _isOnHold = !_isOnHold; }); - } catch (e) { - } + } catch (e) {} } - _selectAudioDevice(AudioDevice device) async { + _selectAudioDevice(VIAudioDevice device) async { await _audioDeviceManager.selectAudioDevice(device); } _showAvailableAudioDevices() async { - List availableAudioDevices = + List availableAudioDevices = await _audioDeviceManager.getAudioDevices(); return showDialog( context: context, diff --git a/audio_call/lib/screens/incoming_call_screen.dart b/audio_call/lib/screens/incoming_call_screen.dart index 919db35..812ce47 100644 --- a/audio_call/lib/screens/incoming_call_screen.dart +++ b/audio_call/lib/screens/incoming_call_screen.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'dart:io'; @@ -11,6 +11,7 @@ import 'package:audio_call/utils/screen_arguments.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:flutter_voximplant/flutter_voximplant.dart'; class IncomingCallScreen extends StatelessWidget { static const routeName = '/incomingCall'; @@ -21,7 +22,7 @@ class IncomingCallScreen extends StatelessWidget { _callService.bind(onCallDisconnected: _onCallDisconnected); } - _onCallDisconnected(Map headers, bool answeredElsewhere) { + _onCallDisconnected(VICall call, Map headers, bool answeredElsewhere) { GetIt locator = GetIt.instance; locator().navigateTo(MainScreen.routeName); } diff --git a/audio_call/lib/screens/login_screen.dart b/audio_call/lib/screens/login_screen.dart index 3919e34..ff26676 100644 --- a/audio_call/lib/screens/login_screen.dart +++ b/audio_call/lib/screens/login_screen.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'package:audio_call/screens/main_screen.dart'; import 'package:audio_call/services/auth_service.dart'; diff --git a/audio_call/lib/screens/main_screen.dart b/audio_call/lib/screens/main_screen.dart index e87c812..0af2e8e 100644 --- a/audio_call/lib/screens/main_screen.dart +++ b/audio_call/lib/screens/main_screen.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'dart:io'; diff --git a/audio_call/lib/services/auth_service.dart b/audio_call/lib/services/auth_service.dart index 662b3ee..a6fcb89 100644 --- a/audio_call/lib/services/auth_service.dart +++ b/audio_call/lib/services/auth_service.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'dart:io'; import 'dart:convert'; @@ -11,7 +11,7 @@ import 'package:shared_preferences/shared_preferences.dart'; typedef void ConnectionClosed(); class AuthService { - Client _client; + VIClient _client; String _displayName; String get displayName => _displayName; @@ -41,7 +41,7 @@ class AuthService { CallService().client = _client; _client.clientStateStream.listen((state) { print('AuthService: client state is changed: $state'); - if (state == ClientState.Disconnected && onConnectionClosed != null) { + if (state == VIClientState.Disconnected && onConnectionClosed != null) { onConnectionClosed(); } }); @@ -49,14 +49,14 @@ class AuthService { Future loginWithPassword(String username, String password) async { print('AuthService: loginWithPassword'); - ClientState clientState = await _client.getClientState(); - if (clientState == ClientState.LoggedIn) { + VIClientState clientState = await _client.getClientState(); + if (clientState == VIClientState.LoggedIn) { return _displayName; } - if (clientState == ClientState.Disconnected) { + if (clientState == VIClientState.Disconnected) { await _client.connect(); } - AuthResult authResult = await _client.login(username, password); + VIAuthResult authResult = await _client.login(username, password); await _saveAuthDetails(username, authResult.loginTokens); _displayName = authResult.displayName; return _displayName; @@ -64,18 +64,18 @@ class AuthService { Future loginWithAccessToken([String username]) async { print('AuthService: loginWithAccessToken'); - ClientState clientState = await _client.getClientState(); - if (clientState == ClientState.LoggedIn) { + VIClientState clientState = await _client.getClientState(); + if (clientState == VIClientState.LoggedIn) { return _displayName; } - if (clientState == ClientState.Disconnected) { + if (clientState == VIClientState.Disconnected) { await _client.connect(); } SharedPreferences prefs = await SharedPreferences.getInstance(); - LoginTokens loginTokens = _getAuthDetails(prefs); + VILoginTokens loginTokens = _getAuthDetails(prefs); String user = username ?? prefs.getString('username'); - AuthResult authResult = + VIAuthResult authResult = await _client.loginWithAccessToken(user, loginTokens.accessToken); await _saveAuthDetails(user, authResult.loginTokens); _displayName = authResult.displayName; @@ -92,7 +92,7 @@ class AuthService { } Future _saveAuthDetails(String username, - LoginTokens loginTokens) async { + VILoginTokens loginTokens) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setString('username', username); prefs.setString('accessToken', loginTokens.accessToken); @@ -101,8 +101,8 @@ class AuthService { prefs.setInt('refreshExpire', loginTokens.refreshExpire); } - LoginTokens _getAuthDetails(SharedPreferences prefs) { - LoginTokens loginTokens = LoginTokens(); + VILoginTokens _getAuthDetails(SharedPreferences prefs) { + VILoginTokens loginTokens = VILoginTokens(); loginTokens.accessToken = prefs.getString('accessToken'); loginTokens.accessExpire = prefs.getInt('accessExpire'); loginTokens.refreshExpire = prefs.getInt('refreshExpire'); diff --git a/audio_call/lib/services/call_service.dart b/audio_call/lib/services/call_service.dart index cae5d92..a390ea8 100644 --- a/audio_call/lib/services/call_service.dart +++ b/audio_call/lib/services/call_service.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'dart:io'; import 'package:audio_call/services/call_service_android.dart'; @@ -9,35 +9,32 @@ import 'callkit_service.dart'; typedef void CallMuted(bool muted); typedef void CallPutOnHold(bool onHold); - - class CallService { static CallService _singleton; - bool navigateToIncomingCallScreen = false; - Client _client; - set client(Client client) { + VIClient _client; + set client(VIClient client) { print('CallService: setClient'); _client = client; configure(); _client.onIncomingCall = onIncomingCall; } - Call call; + VICall call; String get callId => call?.callId; - CallDisconnected onCallDisconnectedEvent; - CallFailed onCallFailedEvent; - CallConnected onCallConnectedEvent; - CallRinging onCallRingingEvent; - CallAudioStarted onCallAudioStartedEvent; - SIPInfoReceived onSIPInfoReceivedEvent; - MessageReceived onMessageReceivedEvent; - ICECompleted onICECompletedEvent; - ICETimeout onICETimeoutEvent; - EndpointAdded onEndpointAddedEvent; + VICallDisconnected onCallDisconnectedEvent; + VICallFailed onCallFailedEvent; + VICallConnected onCallConnectedEvent; + VICallRinging onCallRingingEvent; + VICallAudioStarted onCallAudioStartedEvent; + VISIPInfoReceived onSIPInfoReceivedEvent; + VIMessageReceived onMessageReceivedEvent; + VIICECompleted onICECompletedEvent; + VIICETimeout onICETimeoutEvent; + VIEndpointAdded onEndpointAddedEvent; CallMuted onCallMutedEvent; CallPutOnHold onCallPutOnHoldEvent; @@ -68,13 +65,14 @@ class CallService { Future configure() async {} Future makeAudioCall(String number) async { - Call call = await _client.call(number); + VICall call = await _client.call(number); registerCall(call); print('CallService: created call: ${call.callId}'); return call.callId; } - onIncomingCall(Call call, Map headers) async { + onIncomingCall(VIClient client, VICall call, bool video, + Map headers) async { print('CallService: onIncomingCall(${call.callId})'); if (this.call != null) { await call.decline(); @@ -84,7 +82,7 @@ class CallService { } //#region Call events - registerCall(Call call) { + registerCall(VICall call) { this.call = call; this.call.onCallDisconnected = onCallDisconnected; this.call.onCallFailed = onCallFailed; @@ -99,16 +97,16 @@ class CallService { } void bind({ - CallDisconnected onCallDisconnected, - CallFailed onCallFailed, - CallConnected onCallConnected, - CallRinging onCallRinging, - CallAudioStarted onCallAudioStarted, - SIPInfoReceived onSIPInfoReceived, - MessageReceived onMessageReceived, - ICECompleted onICECompleted, - ICETimeout onICETimeout, - EndpointAdded onEndpointAdded, + VICallDisconnected onCallDisconnected, + VICallFailed onCallFailed, + VICallConnected onCallConnected, + VICallRinging onCallRinging, + VICallAudioStarted onCallAudioStarted, + VISIPInfoReceived onSIPInfoReceived, + VIMessageReceived onMessageReceived, + VIICECompleted onICECompleted, + VIICETimeout onICETimeout, + VIEndpointAdded onEndpointAdded, CallMuted onCallMuted, CallPutOnHold onCallPutOnHold, }) { @@ -126,77 +124,80 @@ class CallService { onCallPutOnHoldEvent = onCallPutOnHold; } - onCallDisconnected(Map headers, bool answeredElsewhere) { + onCallDisconnected( + VICall call, Map headers, bool answeredElsewhere) { print('CallService: onCallDisconnected($headers, $answeredElsewhere)'); - call = null; + this.call = null; if (onCallDisconnectedEvent != null) { - onCallDisconnectedEvent(headers, answeredElsewhere); + onCallDisconnectedEvent(call, headers, answeredElsewhere); } } - onCallFailed(int code, String description, Map headers) { + onCallFailed( + VICall call, int code, String description, Map headers) { print('CallService: onCallFailed($code, $description, $headers)'); - call = null; + this.call = null; if (onCallFailedEvent != null) { - onCallFailedEvent(code, description, headers); + onCallFailedEvent(call, code, description, headers); } } - onCallConnected(Map headers) { + onCallConnected(VICall call, Map headers) { print('CallService: onCallConnected($headers)'); if (onCallConnectedEvent != null) { - onCallConnectedEvent(headers); + onCallConnectedEvent(call, headers); } } - onCallRinging(Map headers) { + onCallRinging(VICall call, Map headers) { print('CallService: onCallRinging($headers)'); if (onCallRingingEvent != null) { - onCallRingingEvent(headers); + onCallRingingEvent(call, headers); } } - onCallAudioStarted() { + onCallAudioStarted(VICall call) { print('CallService: onCallAudioStarted()'); if (onCallAudioStartedEvent != null) { - onCallAudioStartedEvent(); + onCallAudioStartedEvent(call); } } - onSIPInfoReceived(String type, String content, Map headers) { + onSIPInfoReceived( + VICall call, String type, String content, Map headers) { print('CallService: onSIPInfoReceived($type, $content, $headers)'); if (onSIPInfoReceivedEvent != null) { - onSIPInfoReceivedEvent(type, content, headers); + onSIPInfoReceivedEvent(call, type, content, headers); } } - onMessageReceived(String message) { + onMessageReceived(VICall call, String message) { print('CallScreen: onMessageReceived($message)'); if (onMessageReceivedEvent != null) { - onMessageReceivedEvent(message); + onMessageReceivedEvent(call, message); } } - onICETimeout() { + onICETimeout(VICall call) { print('CallService: onICETimeout()'); if (onICETimeoutEvent != null) { - onICETimeoutEvent(); + onICETimeoutEvent(call); } } - onICECompleted() { + onICECompleted(VICall call) { print('CallService: onICECompleted()'); if (onICECompletedEvent != null) { - onICECompletedEvent(); + onICECompletedEvent(call); } } - onEndpointAdded(Endpoint endpoint) { + onEndpointAdded(VICall call, VIEndpoint endpoint) { print('CallService: onEndpointAdded($endpoint)'); endpoint.onEndpointUpdated = onEndpointUpdated; } - onEndpointUpdated(Endpoint endpoint) { + onEndpointUpdated(VIEndpoint endpoint) { print('CallService: onEndpointUpdated($endpoint)'); } @@ -224,4 +225,3 @@ class CallService { } //#endregion } - diff --git a/audio_call/lib/services/call_service_android.dart b/audio_call/lib/services/call_service_android.dart index 60d520b..93570d8 100644 --- a/audio_call/lib/services/call_service_android.dart +++ b/audio_call/lib/services/call_service_android.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'package:audio_call/screens/incoming_call_screen.dart'; import 'package:audio_call/services/call_service.dart'; @@ -14,12 +14,14 @@ class CallServiceAndroid extends CallService { CallServiceAndroid() : super.ctr(); @override - onIncomingCall(Call call, Map headers) async { + onIncomingCall(VIClient client, VICall call, bool video, + Map headers) async { print('CallServiceAndroid: onIncomingCall(${call.callId})'); - super.onIncomingCall(call, headers); + super.onIncomingCall(client, call, video, headers); if (AppStateHelper().appState == AppState.Foreground) { - print('CallServiceAndroid: onIncomingCall: navifate to Incoming call screen'); + print( + 'CallServiceAndroid: onIncomingCall: navifate to Incoming call screen'); GetIt locator = GetIt.instance; locator().navigateTo(IncomingCallScreen.routeName, arguments: CallArguments.withDisplayName( diff --git a/audio_call/lib/services/callkit_service.dart b/audio_call/lib/services/callkit_service.dart index ca8fae5..78194ed 100644 --- a/audio_call/lib/services/callkit_service.dart +++ b/audio_call/lib/services/callkit_service.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'package:audio_call/screens/call_screen.dart'; import 'package:audio_call/utils/screen_arguments.dart'; @@ -16,7 +16,8 @@ class CallServiceIOS extends CallService { final Uuid _uuid = Uuid(); final FlutterCallKit _callKit = FlutterCallKit(); - final AudioDeviceManager _audioDeviceManager = Voximplant().getAudioDeviceManager(); + final VIAudioDeviceManager _audioDeviceManager = + Voximplant().getAudioDeviceManager(); CallServiceIOS() : super.ctr(); @@ -42,7 +43,8 @@ class CallServiceIOS extends CallService { } @override - onIncomingCall(Call call, Map headers) async { + onIncomingCall(VIClient client, VICall call, bool video, + Map headers) async { if (this.call != null) { await call.decline(); return; @@ -51,7 +53,8 @@ class CallServiceIOS extends CallService { print('onIncomingCall($call)'); if (_callKitUUID == null) { _callKitUUID = call.callKitUUID; - _callKit.displayIncomingCall(call.callKitUUID, + _callKit.displayIncomingCall( + call.callKitUUID, call.endpoints?.first?.displayName, call.endpoints?.first?.displayName, handleType: HandleType.generic); @@ -83,39 +86,41 @@ class CallServiceIOS extends CallService { } @override - onCallConnected(Map headers) { - super.onCallConnected(headers); + onCallConnected(VICall call, Map headers) { + super.onCallConnected(call, headers); print('CallServiceIOS: onCallConnected($headers)'); - _callKit.updateDisplay(_callKitUUID, call.callId, - call.endpoints.first?.userName ?? 'Unknown', + _callKit.updateDisplay( + _callKitUUID, call.callId, call.endpoints.first?.userName ?? 'Unknown', handleType: HandleType.generic); } @override - onCallDisconnected(Map headers, bool answeredElsewhere) { + onCallDisconnected( + VICall call, Map headers, bool answeredElsewhere) { print('CallServiceIOS: onCallDisconnected($headers)'); _audioDeviceManager.callKitReleaseAudioSession(); _callKit.reportEndCallWithUUID(_callKitUUID, EndReason.remoteEnded); _callKitUUID = null; - super.onCallDisconnected(headers, answeredElsewhere); + super.onCallDisconnected(call, headers, answeredElsewhere); } @override - onCallFailed(int code, String description, Map headers) { + onCallFailed( + VICall call, int code, String description, Map headers) { print('CallServiceIOS: onCallFailed($description)'); _audioDeviceManager.callKitReleaseAudioSession(); _callKit.endCall(_callKitUUID); _callKitUUID = null; - super.onCallFailed(code, description, headers); + super.onCallFailed(call, code, description, headers); } @override - onEndpointUpdated(Endpoint endpoint) { + onEndpointUpdated(VIEndpoint endpoint) { super.onEndpointUpdated(endpoint); print('CallServiceIOS: onEndpointUpdated($endpoint)'); - _callKit.updateDisplay(_callKitUUID, call.callId, - call.endpoints.first?.userName ?? 'Unknown', + _callKit.updateDisplay( + _callKitUUID, call.callId, call.endpoints.first?.userName ?? 'Unknown', handleType: HandleType.generic); } @@ -128,7 +133,9 @@ class CallServiceIOS extends CallService { if (this.call == null && _callKitUUID == null) { _callKitUUID = uuid; } - if (call != null && _callKitUUID != null && call.callKitUUID != _callKitUUID) { + if (call != null && + _callKitUUID != null && + call.callKitUUID != _callKitUUID) { await _callKit.endCall(uuid); } } @@ -144,7 +151,8 @@ class CallServiceIOS extends CallService { } Future _didReceiveStartCallAction(String uuid, String handle) async { - print('CallServiceIOS: didReceiveStartCallAction(uuid: $uuid, handle: $handle)'); + print( + 'CallServiceIOS: didReceiveStartCallAction(uuid: $uuid, handle: $handle)'); await _audioDeviceManager.callKitConfigureAudioSession(); } @@ -155,7 +163,8 @@ class CallServiceIOS extends CallService { Future _didPerformSetMutedCallAction(bool mute, String uuid) async { // Called when the system or user mutes a call - print('CallServiceIOS: didPerformSetMutedCallAction(mute: $mute, uuid: $uuid)'); + print( + 'CallServiceIOS: didPerformSetMutedCallAction(mute: $mute, uuid: $uuid)'); await super.sendAudio(!mute); if (onCallMutedEvent != null) { onCallMutedEvent(mute); diff --git a/audio_call/lib/services/navigation_service.dart b/audio_call/lib/services/navigation_service.dart index d3d786c..df3689a 100644 --- a/audio_call/lib/services/navigation_service.dart +++ b/audio_call/lib/services/navigation_service.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'package:audio_call/utils/screen_arguments.dart'; import 'package:flutter/material.dart'; diff --git a/audio_call/lib/services/push_service.dart b/audio_call/lib/services/push_service.dart index f201dca..b6461b0 100644 --- a/audio_call/lib/services/push_service.dart +++ b/audio_call/lib/services/push_service.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'dart:io'; import 'dart:convert'; diff --git a/audio_call/lib/theme/voximplant_theme.dart b/audio_call/lib/theme/voximplant_theme.dart index 79de74e..cf531ed 100644 --- a/audio_call/lib/theme/voximplant_theme.dart +++ b/audio_call/lib/theme/voximplant_theme.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'package:flutter/material.dart'; diff --git a/audio_call/lib/utils/app_state_helper.dart b/audio_call/lib/utils/app_state_helper.dart index b3655cd..b92abb8 100644 --- a/audio_call/lib/utils/app_state_helper.dart +++ b/audio_call/lib/utils/app_state_helper.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. enum AppState { NotLaunched, diff --git a/audio_call/lib/utils/notifications_android.dart b/audio_call/lib/utils/notifications_android.dart index b5b3d33..b323e35 100644 --- a/audio_call/lib/utils/notifications_android.dart +++ b/audio_call/lib/utils/notifications_android.dart @@ -1,4 +1,4 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'dart:async'; diff --git a/audio_call/lib/utils/screen_arguments.dart b/audio_call/lib/utils/screen_arguments.dart index ca3c234..8b77d30 100644 --- a/audio_call/lib/utils/screen_arguments.dart +++ b/audio_call/lib/utils/screen_arguments.dart @@ -1,10 +1,10 @@ -/// Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. import 'package:flutter_voximplant/flutter_voximplant.dart'; class CallArguments { - Call call; + VICall call; String displayName; String callId; diff --git a/audio_call/pubspec.lock b/audio_call/pubspec.lock index 422b111..ba94f01 100644 --- a/audio_call/pubspec.lock +++ b/audio_call/pubspec.lock @@ -70,7 +70,7 @@ packages: name: firebase_messaging url: "https://pub.dartlang.org" source: hosted - version: "6.0.1" + version: "6.0.9" flutter: dependency: "direct main" description: flutter @@ -110,7 +110,7 @@ packages: name: flutter_voximplant url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" get_it: dependency: "direct main" description: @@ -265,4 +265,4 @@ packages: version: "3.5.0" sdks: dart: ">=2.4.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.10.0 <2.0.0" diff --git a/audio_call/pubspec.yaml b/audio_call/pubspec.yaml index 2467e05..6e9638e 100644 --- a/audio_call/pubspec.yaml +++ b/audio_call/pubspec.yaml @@ -19,14 +19,14 @@ environment: dependencies: flutter: sdk: flutter - flutter_voximplant: 1.2.0 + flutter_voximplant: 2.0.0 shared_preferences: 0.5.3+4 crypto: 2.1.3 get_it: 3.0.0 uuid: 2.0.2 permission_handler: '^3.2.0' flutter_voip_push_notification: 0.0.3 - firebase_messaging: 6.0.1 + firebase_messaging: 6.0.9 flutter_local_notifications: 0.8.4+3 flutter_call_kit: git: diff --git a/video_call/.gitignore b/video_call/.gitignore new file mode 100644 index 0000000..ae1f183 --- /dev/null +++ b/video_call/.gitignore @@ -0,0 +1,37 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/video_call/.metadata b/video_call/.metadata new file mode 100644 index 0000000..1b5cec0 --- /dev/null +++ b/video_call/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 27321ebbad34b0a3fafe99fac037102196d655ff + channel: stable + +project_type: app diff --git a/video_call/README.md b/video_call/README.md new file mode 100644 index 0000000..96449ee --- /dev/null +++ b/video_call/README.md @@ -0,0 +1,47 @@ +# video_call + +This demo demonstrates basic video call functionality of the Voximplant Flutter SDK. +It is possible to make video calls with any application (mobile or web) that have integrated +Voximplant SDKs. + +This demo application doesn't handle push notifications, so it doesn't receive incoming +calls if the application is in the background or killed. + +## Features + +The application is able to: + +- log in to the Voximplant Cloud +- make a video call +- receive an incoming call +- put a call on hold / take it off hold +- stop/start sending video during a call + +## Getting started + +To get started, you'll need to register a free Voximplant developer account. + +You'll need the following: + +- Voximplant application +- two Voximplant users +- VoxEngine scenario +- routing setup + +#### VoxEngine scenario example +```js +VoxEngine.addEventListener(AppEvents.CallAlerting, (e) => { +const newCall = VoxEngine.callUserDirect( + e.call, + e.destination, + e.callerid, + e.displayName, + null +); +VoxEngine.easyProcess(e.call, newCall, ()=>{}, true); +}); +``` + +## Instaling +1. Clone this repo +2. Run `flutter pub get` diff --git a/video_call/android/.gitignore b/video_call/android/.gitignore new file mode 100644 index 0000000..bc2100d --- /dev/null +++ b/video_call/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/video_call/android/app/build.gradle b/video_call/android/app/build.gradle new file mode 100644 index 0000000..f30a585 --- /dev/null +++ b/video_call/android/app/build.gradle @@ -0,0 +1,69 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +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' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "com.voximplant.flutter.videocall" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // 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 + } + } + + packagingOptions { + exclude 'META-INF/proguard/androidx-annotations.pro' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/video_call/android/app/src/debug/AndroidManifest.xml b/video_call/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..0bc8220 --- /dev/null +++ b/video_call/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/video_call/android/app/src/main/AndroidManifest.xml b/video_call/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2e3dce0 --- /dev/null +++ b/video_call/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/video_call/android/app/src/main/java/com/voximplant/flutter/videocall/MainActivity.java b/video_call/android/app/src/main/java/com/voximplant/flutter/videocall/MainActivity.java new file mode 100644 index 0000000..cb885a5 --- /dev/null +++ b/video_call/android/app/src/main/java/com/voximplant/flutter/videocall/MainActivity.java @@ -0,0 +1,13 @@ +package com.voximplant.flutter.videocall; + +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + GeneratedPluginRegistrant.registerWith(flutterEngine); + } +} diff --git a/video_call/android/app/src/main/res/drawable/launch_background.xml b/video_call/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..1071587 --- /dev/null +++ b/video_call/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/video_call/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/video_call/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/video_call/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/video_call/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/video_call/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/video_call/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..13a3686 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..24a0e07 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..17c2457 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..5b45375 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3996bbf Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..c5c8c24 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..dc8b10d Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..cdb8d02 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d0afb5b Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..33b3ac3 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..b36e383 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..ccf25b2 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..02f0e1c Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..c488e62 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..08d6641 Binary files /dev/null and b/video_call/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/video_call/android/app/src/main/res/values/colors.xml b/video_call/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c3ed4b8 --- /dev/null +++ b/video_call/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #1c0b43 + \ No newline at end of file diff --git a/video_call/android/app/src/main/res/values/ic_launcher_background.xml b/video_call/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/video_call/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/video_call/android/app/src/main/res/values/styles.xml b/video_call/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/video_call/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/video_call/android/app/src/profile/AndroidManifest.xml b/video_call/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..0bc8220 --- /dev/null +++ b/video_call/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/video_call/android/build.gradle b/video_call/android/build.gradle new file mode 100644 index 0000000..e0d7ae2 --- /dev/null +++ b/video_call/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/video_call/android/gradle.properties b/video_call/android/gradle.properties new file mode 100644 index 0000000..38c8d45 --- /dev/null +++ b/video_call/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/video_call/android/settings.gradle b/video_call/android/settings.gradle new file mode 100644 index 0000000..5a2f14f --- /dev/null +++ b/video_call/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/video_call/ios/.gitignore b/video_call/ios/.gitignore new file mode 100644 index 0000000..e96ef60 --- /dev/null +++ b/video_call/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/video_call/ios/Flutter/AppFrameworkInfo.plist b/video_call/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6b4c0f7 --- /dev/null +++ b/video_call/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/video_call/ios/Flutter/Debug.xcconfig b/video_call/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/video_call/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/video_call/ios/Flutter/Release.xcconfig b/video_call/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/video_call/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/video_call/ios/Podfile b/video_call/ios/Podfile new file mode 100644 index 0000000..98a90b8 --- /dev/null +++ b/video_call/ios/Podfile @@ -0,0 +1,87 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/video_call/ios/Podfile.lock b/video_call/ios/Podfile.lock new file mode 100644 index 0000000..817b61b --- /dev/null +++ b/video_call/ios/Podfile.lock @@ -0,0 +1,59 @@ +PODS: + - Flutter (1.0.0) + - flutter_voximplant (2.0.0): + - Flutter + - VoxImplantSDK (= 2.26.0) + - permission_handler (3.2.0): + - Flutter + - shared_preferences (0.0.1): + - Flutter + - shared_preferences_macos (0.0.1): + - Flutter + - shared_preferences_web (0.0.1): + - Flutter + - VoxImplantSDK (2.26.0): + - VoxImplantSDK/Core (= 2.26.0) + - VoxImplantSDK/Core (2.26.0): + - VoxImplantWebRTC (= 78.0.0) + - VoxImplantWebRTC (78.0.0) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - flutter_voximplant (from `.symlinks/plugins/flutter_voximplant/ios`) + - permission_handler (from `.symlinks/plugins/permission_handler/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) + - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) + +SPEC REPOS: + trunk: + - VoxImplantSDK + - VoxImplantWebRTC + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + flutter_voximplant: + :path: ".symlinks/plugins/flutter_voximplant/ios" + permission_handler: + :path: ".symlinks/plugins/permission_handler/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/ios" + shared_preferences_macos: + :path: ".symlinks/plugins/shared_preferences_macos/ios" + shared_preferences_web: + :path: ".symlinks/plugins/shared_preferences_web/ios" + +SPEC CHECKSUMS: + Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_voximplant: 5d2223da5d47ff2ee5f43c84a60c8fc4f5a01ffa + permission_handler: d4838243e7d0f4b18911a6422d81e7e6b71ad545 + shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 + shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 + shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 + VoxImplantSDK: c53bd6ed2634a43a33ae0d517cc90c7ae4b0bed4 + VoxImplantWebRTC: a4bd73d41b9f8f3a60de032337b661bab8ead6f9 + +PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271 + +COCOAPODS: 1.8.3 diff --git a/video_call/ios/Runner.xcodeproj/project.pbxproj b/video_call/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5294a25 --- /dev/null +++ b/video_call/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,525 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 821ABEDD885EAAF31B41C12B /* Pods */ = { + isa = PBXGroup; + children = ( + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 821ABEDD885EAAF31B41C12B /* Pods */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = W9BHJBL635; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = W9BHJBL635; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.voximplant.flutter.videocall; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = W9BHJBL635; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.voximplant.flutter.videocall; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = W9BHJBL635; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.voximplant.flutter.videocall; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/video_call/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/video_call/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/video_call/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/video_call/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/video_call/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/video_call/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/video_call/ios/Runner.xcworkspace/contents.xcworkspacedata b/video_call/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/video_call/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/video_call/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/video_call/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/video_call/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/video_call/ios/Runner/AppDelegate.h b/video_call/ios/Runner/AppDelegate.h new file mode 100644 index 0000000..36e21bb --- /dev/null +++ b/video_call/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/video_call/ios/Runner/AppDelegate.m b/video_call/ios/Runner/AppDelegate.m new file mode 100644 index 0000000..70e8393 --- /dev/null +++ b/video_call/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..1851bdf --- /dev/null +++ b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom": "iphone", + "filename" : "voximplant-20@2x.png", + "scale": "2x" + }, + { + "size" : "20x20", + "idiom": "iphone", + "filename" : "voximplant-20@3x.png", + "scale": "3x" + }, + { + "size" : "20x20", + "idiom": "ipad", + "filename" : "voximplant-20.png", + "scale": "1x" + }, + { + "size" : "20x20", + "idiom": "ipad", + "filename" : "voximplant-20@2x.png", + "scale": "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "voximplant-29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "voximplant-29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "voximplant-40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "voximplant-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "voximplant-60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "voximplant-60@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "voximplant-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "voximplant-29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "voximplant-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "voximplant-40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "voximplant-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "voximplant-76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "voximplant-83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "voximplant-1024.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-1024.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-1024.png new file mode 100644 index 0000000..a21852e Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-1024.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20.png new file mode 100644 index 0000000..9e4ce38 Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20@2x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20@2x.png new file mode 100644 index 0000000..76e874c Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20@2x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20@3x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20@3x.png new file mode 100644 index 0000000..861a1e8 Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-20@3x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29.png new file mode 100644 index 0000000..b8336e7 Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29@2x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29@2x.png new file mode 100644 index 0000000..ce883db Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29@2x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29@3x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29@3x.png new file mode 100644 index 0000000..7dcab77 Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-29@3x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40.png new file mode 100644 index 0000000..76e874c Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40@2x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40@2x.png new file mode 100644 index 0000000..cdaaf00 Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40@2x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40@3x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40@3x.png new file mode 100644 index 0000000..2e0ea1b Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-40@3x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-60@2x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-60@2x.png new file mode 100644 index 0000000..2e0ea1b Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-60@2x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-60@3x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-60@3x.png new file mode 100644 index 0000000..7e64d7f Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-60@3x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-76.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-76.png new file mode 100644 index 0000000..a9fdeee Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-76.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-76@2x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-76@2x.png new file mode 100644 index 0000000..94c162d Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-76@2x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-83.5@2x.png b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-83.5@2x.png new file mode 100644 index 0000000..505b2ce Binary files /dev/null and b/video_call/ios/Runner/Assets.xcassets/AppIcon.appiconset/voximplant-83.5@2x.png differ diff --git a/video_call/ios/Runner/Assets.xcassets/Contents.json b/video_call/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/video_call/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/video_call/ios/Runner/Base.lproj/LaunchScreen.storyboard b/video_call/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..0ab9a4c --- /dev/null +++ b/video_call/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/video_call/ios/Runner/Base.lproj/Main.storyboard b/video_call/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/video_call/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/video_call/ios/Runner/Info.plist b/video_call/ios/Runner/Info.plist new file mode 100644 index 0000000..4d8ba2e --- /dev/null +++ b/video_call/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + video_call + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSCameraUsageDescription + To make video calls + NSMicrophoneUsageDescription + To make video calls + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/video_call/ios/Runner/main.m b/video_call/ios/Runner/main.m new file mode 100644 index 0000000..dff6597 --- /dev/null +++ b/video_call/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/video_call/lib/active_call/active_call.dart b/video_call/lib/active_call/active_call.dart new file mode 100644 index 0000000..7e94a22 --- /dev/null +++ b/video_call/lib/active_call/active_call.dart @@ -0,0 +1,5 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'active_call_page.dart'; +export 'active_call_page_arguments.dart'; +export 'bloc/bloc.dart'; diff --git a/video_call/lib/active_call/active_call_page.dart b/video_call/lib/active_call/active_call_page.dart new file mode 100644 index 0000000..e0ecd3b --- /dev/null +++ b/video_call/lib/active_call/active_call_page.dart @@ -0,0 +1,242 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_voximplant/flutter_voximplant.dart'; +import 'package:video_call/active_call/active_call.dart'; +import 'package:video_call/call_failed/call_failed.dart'; +import 'package:video_call/routes.dart'; +import 'package:video_call/theme/voximplant_theme.dart'; +import 'package:video_call/widgets/widgets.dart'; + +import 'bloc/active_call_state.dart'; + +class ActiveCallPage extends StatefulWidget { + @override + State createState() { + return _ActiveCallPageState(); + } +} + +class _ActiveCallPageState extends State { + String callState = 'Connecting'; + VIVideoViewController _localVideoViewController = VIVideoViewController(); + VIVideoViewController _remoteVideoViewController = VIVideoViewController(); + double _localVideoAspectRatio = 1.0; + double _remoteVideoAspectRatio = 1.0; + bool isOnHold = false; + bool isSendingVideo = true; + ActiveCallPageArguments _arguments; + + void _localVideoHasChanged() { + setState(() { + _localVideoAspectRatio = _localVideoViewController.aspectRatio; + }); + } + + void _remoteVideoHasChanged() { + setState(() { + _remoteVideoAspectRatio = _remoteVideoViewController.aspectRatio; + }); + } + + @override + void initState() { + super.initState(); + _localVideoViewController.addListener(_localVideoHasChanged); + _remoteVideoViewController.addListener(_remoteVideoHasChanged); + } + + @override + void didChangeDependencies(){ + super.didChangeDependencies(); + _arguments = + ModalRoute.of(context).settings.arguments; + if (_arguments.isIncoming) { + BlocProvider.of(context).add(AnswerIncomingCall()); + } else { + BlocProvider.of(context) + .add(StartOutgoingCall(callTo: _arguments.callTo)); + } + } + + @override + void dispose() { + _localVideoViewController.removeListener(_localVideoHasChanged); + _remoteVideoViewController.removeListener(_remoteVideoHasChanged); + _localVideoViewController.dispose(); + _remoteVideoViewController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + void _hangup() { + BlocProvider.of(context).add(EndCall()); + } + + void _hold() { + BlocProvider.of(context).add(HoldCall(doHold: !isOnHold)); + } + + void _sendVideo() { + BlocProvider.of(context) + .add(SendVideo(doSend: !isSendingVideo)); + } + + void _switchCamera() { + BlocProvider.of(context).add(SwitchCamera()); + } + + return BlocListener( + listener: (context, state) { + if (state is ActiveCallDisconnected) { + Navigator.of(context).pushReplacementNamed(AppRoutes.makeCall); + } + if (state is ActiveCallFailed) { + Navigator.of(context).pushReplacementNamed(AppRoutes.callFailed, + arguments: CallFailedPageArguments(failureReason: state.errorDescription, + endpoint: state.endpoint ?? _arguments.callTo + ) + ); + } + if (state is ActiveCallConnecting) { + setState(() { + callState = 'Connecting'; + }); + } + if (state is ActiveCallRinging) { + setState(() { + callState = 'Ringing'; + }); + } + if (state is ActiveCallConnected) { + setState(() { + callState = 'Connected'; + }); + } + if (state is ActiveCallVideoStreamAdded) { + if (state.isLocal) { + _localVideoViewController.streamId = state.streamId; + } else { + _remoteVideoViewController.streamId = state.streamId; + } + } + if (state is ActiveCallVideoStreamRemoved) { + if (state.isLocal) { + _localVideoViewController.streamId = null; + } else { + _remoteVideoViewController.streamId = null; + } + } + if (state is ActiveCallHold) { + setState(() { + isOnHold = state.isHeld; + }); + } + if (state is ActiveCallSendVideo) { + setState(() { + isSendingVideo = state.isSendingVideo; + }); + } + }, + child: BlocBuilder( + builder: (context, state) { + return Scaffold( + backgroundColor: VoximplantColors.grey, + body: SafeArea( + child: Column( + children: [ + Expanded( + child: Container( + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: AspectRatio( + aspectRatio: _remoteVideoAspectRatio, + child: VIVideoView(_remoteVideoViewController), + ), + ), + Align( + alignment: Alignment.topRight, + child: Padding( + padding: EdgeInsets.all(10), + child: Container( + width: MediaQuery.of(context).size.width / 4, + height: MediaQuery.of(context).size.height / 4, + child: Align( + child: AspectRatio( + aspectRatio: _localVideoAspectRatio, + child: VIVideoView(_localVideoViewController), + ), + ), + ), + ) + ), + Align( + alignment: Alignment.topLeft, + child: Padding( + padding: EdgeInsets.only(left: 20, top: 20), + child: Platform.isAndroid ? + Widgets.iconButton( + icon: Icons.switch_camera, + color: VoximplantColors.button, + tooltip: 'Switch camera', + onPressed: _switchCamera, + ) : + Container() + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Text( + callState, + style: TextStyle(color: VoximplantColors.white), + ), + ) + ], + ), + ), + ), + Container( + height: 80, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Widgets.iconButton( + icon: isSendingVideo + ? Icons.videocam_off + : Icons.videocam, + color: VoximplantColors.button, + tooltip: 'Send video', + onPressed: _sendVideo), + Widgets.iconButton( + icon: Icons.call_end, + color: VoximplantColors.red, + tooltip: 'Hang up', + onPressed: _hangup), + Widgets.iconButton( + icon: isOnHold ? Icons.play_arrow : Icons.pause, + color: VoximplantColors.button, + tooltip: 'Hold', + onPressed: _hold), + ], + ), + ], + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/video_call/lib/active_call/active_call_page_arguments.dart b/video_call/lib/active_call/active_call_page_arguments.dart new file mode 100644 index 0000000..bed7929 --- /dev/null +++ b/video_call/lib/active_call/active_call_page_arguments.dart @@ -0,0 +1,11 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +class ActiveCallPageArguments { + String callTo; + bool isIncoming; + + ActiveCallPageArguments({ + this.isIncoming, + this.callTo + }); +} diff --git a/video_call/lib/active_call/bloc/active_call_bloc.dart b/video_call/lib/active_call/bloc/active_call_bloc.dart new file mode 100644 index 0000000..1687b29 --- /dev/null +++ b/video_call/lib/active_call/bloc/active_call_bloc.dart @@ -0,0 +1,115 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_voximplant/flutter_voximplant.dart'; +import 'package:video_call/active_call/bloc/active_call_event.dart'; +import 'package:video_call/active_call/bloc/active_call_state.dart'; +import 'package:video_call/services/auth_service.dart'; +import 'package:video_call/services/call_service.dart'; +import 'package:video_call/services/call_state.dart'; + +class ActiveCallBloc extends Bloc { + final AuthService authService; + final CallService callService; + String _callId; + VICameraType _cameraType = VICameraType.Front; + + StreamSubscription _callStateSubscription; + + ActiveCallBloc({@required this.authService, this.callService}); + + @override + ActiveCallState get initialState => ActiveCallConnecting(); + + @override + Stream mapEventToState(ActiveCallEvent event) async* { + if (event is StartOutgoingCall) { + try { + _callId = await callService.makeVideoCall(callTo: event.callTo); + _callStateSubscription = callService + .subscribeToCallStateChanges(_callId) + .listen(onCallStateChanged); + } catch (e) { + yield ActiveCallFailed(errorDescription: e, endpoint: event.callTo); + } + } + if (event is AnswerIncomingCall) { + try { + _callId = await callService.answerVideoCall(); + _callStateSubscription = callService + .subscribeToCallStateChanges(_callId) + .listen(onCallStateChanged); + } catch (e) { + yield ActiveCallFailed(errorDescription: e, endpoint: null); + } + } + if (event is EndCall) { + await callService.endCall(_callId); + } + if (event is HoldCall) { + try { + await callService.holdCall(_callId, event.doHold); + yield ActiveCallHold(isHeld: event.doHold, errorDescription: null); + } on VIException catch (e) { + yield ActiveCallHold( + isHeld: !event.doHold, errorDescription: e.message); + } + } + if (event is SendVideo) { + try { + await callService.sendVideo(_callId, event.doSend); + yield ActiveCallSendVideo( + isSendingVideo: event.doSend, errorDescription: null); + } on VIException catch (e) { + yield ActiveCallSendVideo( + isSendingVideo: !event.doSend, errorDescription: e.message); + } + } + if (event is SwitchCamera) { + VICameraType cameraToSwitch = _cameraType == VICameraType.Front ? + VICameraType.Back : VICameraType.Front; + await Voximplant().getCameraManager().selectCamera(cameraToSwitch); + _cameraType = cameraToSwitch; + } + if (event is UpdateCallState) { + CallState callState = event.callState; + if (callState is CallStateRinging) { + yield ActiveCallRinging(); + } + if (callState is CallStateConnected) { + yield ActiveCallConnected(); + } + if (callState is CallStateDisconnected) { + yield ActiveCallDisconnected(); + } + if (callState is CallStateFailed) { + yield ActiveCallFailed( + errorDescription: callState.errorDescription, + endpoint: callState.endpoint); + } + if (callState is CallStateVideoStreamAdded) { + yield ActiveCallVideoStreamAdded( + streamId: callState.streamId, isLocal: callState.isLocal); + } + if (callState is CallStateVideoStreamRemoved) { + yield ActiveCallVideoStreamRemoved( + streamId: callState.streamId, isLocal: callState.isLocal); + } + } + } + + @override + Future close() { + if (_callStateSubscription != null) { + _callStateSubscription.cancel(); + } + return super.close(); + } + + void onCallStateChanged(CallState state) { + add(UpdateCallState(callState: state)); + } +} diff --git a/video_call/lib/active_call/bloc/active_call_event.dart b/video_call/lib/active_call/bloc/active_call_event.dart new file mode 100644 index 0000000..29d112d --- /dev/null +++ b/video_call/lib/active_call/bloc/active_call_event.dart @@ -0,0 +1,66 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:meta/meta.dart'; +import 'package:equatable/equatable.dart'; +import 'package:video_call/services/call_state.dart'; + +abstract class ActiveCallEvent extends Equatable { + const ActiveCallEvent(); + + @override + List get props => []; +} + +class StartOutgoingCall extends ActiveCallEvent { + final String callTo; + + StartOutgoingCall({@required this.callTo}); + + @override + List get props => [callTo]; + + @override + String toString() => 'StartOutgoingCall: callTo: $callTo'; +} + +class AnswerIncomingCall extends ActiveCallEvent {} + +class EndCall extends ActiveCallEvent {} + +class UpdateCallState extends ActiveCallEvent { + final CallState callState; + + UpdateCallState({@required this.callState}); + + @override + List get props => [callState]; + + @override + String toString() => 'UpdateCallState: callState: $callState'; +} + +class HoldCall extends ActiveCallEvent { + final bool doHold; + + HoldCall({@required this.doHold}); + + @override + List get props => [doHold]; + + @override + String toString() => 'HoldCall: doHold: $doHold'; +} + +class SendVideo extends ActiveCallEvent { + final bool doSend; + + SendVideo({@required this.doSend}); + + @override + List get props => [doSend]; + + @override + String toString() => 'SendVideo: doSend: $doSend'; +} + +class SwitchCamera extends ActiveCallEvent {} diff --git a/video_call/lib/active_call/bloc/active_call_state.dart b/video_call/lib/active_call/bloc/active_call_state.dart new file mode 100644 index 0000000..3786fb9 --- /dev/null +++ b/video_call/lib/active_call/bloc/active_call_state.dart @@ -0,0 +1,87 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:meta/meta.dart'; +import 'package:equatable/equatable.dart'; + +abstract class ActiveCallState extends Equatable { + const ActiveCallState(); + @override + List get props => []; +} + +class ActiveCallConnecting extends ActiveCallState {} + +class ActiveCallRinging extends ActiveCallState {} + +class ActiveCallConnected extends ActiveCallState {} + +class ActiveCallDisconnected extends ActiveCallState {} + +class ActiveCallVideoStreamAdded extends ActiveCallState { + final String streamId; + final bool isLocal; + ActiveCallVideoStreamAdded({@required this.streamId, @required this.isLocal}); + + @override + List get props => [streamId, isLocal]; + + @override + String toString() => + 'ActiveCallVideoStreamAdded: streamId: $streamId, isLocal: $isLocal'; +} + +class ActiveCallVideoStreamRemoved extends ActiveCallState { + final String streamId; + final bool isLocal; + ActiveCallVideoStreamRemoved( + {@required this.streamId, @required this.isLocal}); + + @override + List get props => [streamId, isLocal]; + + @override + String toString() => + 'ActiveCallVideoStreamRemoved: streamId: $streamId, isLocal: $isLocal'; +} + +class ActiveCallFailed extends ActiveCallState { + final String errorDescription; + final String endpoint; + + ActiveCallFailed({@required this.errorDescription, @required this.endpoint}); + + @override + List get props => [errorDescription, endpoint]; + + @override + String toString() => 'CallFailed: errorDescription: $errorDescription'; +} + +class ActiveCallHold extends ActiveCallState { + final bool isHeld; + final String errorDescription; + + ActiveCallHold({@required this.isHeld, @required this.errorDescription}); + + @override + List get props => [isHeld, errorDescription]; + + @override + String toString() => + 'ActiveCallHold: isHeld: $isHeld, errorDescription: $errorDescription'; +} + +class ActiveCallSendVideo extends ActiveCallState { + final bool isSendingVideo; + final String errorDescription; + + ActiveCallSendVideo( + {@required this.isSendingVideo, @required this.errorDescription}); + + @override + List get props => [isSendingVideo, errorDescription]; + + @override + String toString() => 'ActiveCallSendVideo: isSendingVideo: $isSendingVideo,' + ' errorDescription: $errorDescription'; +} diff --git a/video_call/lib/active_call/bloc/bloc.dart b/video_call/lib/active_call/bloc/bloc.dart new file mode 100644 index 0000000..4223ccc --- /dev/null +++ b/video_call/lib/active_call/bloc/bloc.dart @@ -0,0 +1,5 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'active_call_bloc.dart'; +export 'active_call_event.dart'; +export 'active_call_state.dart'; diff --git a/video_call/lib/call_failed/call_failed.dart b/video_call/lib/call_failed/call_failed.dart new file mode 100644 index 0000000..04f93af --- /dev/null +++ b/video_call/lib/call_failed/call_failed.dart @@ -0,0 +1,4 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'call_failed_page.dart'; +export 'call_failed_page_arguments.dart'; diff --git a/video_call/lib/call_failed/call_failed_page.dart b/video_call/lib/call_failed/call_failed_page.dart new file mode 100644 index 0000000..0387c0f --- /dev/null +++ b/video_call/lib/call_failed/call_failed_page.dart @@ -0,0 +1,59 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter/material.dart'; +import 'package:video_call/routes.dart'; +import 'package:video_call/theme/voximplant_theme.dart'; +import 'package:video_call/widgets/widgets.dart'; + +import 'call_failed_page_arguments.dart'; + +class CallFailedPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final CallFailedPageArguments _arguments = + ModalRoute.of(context).settings.arguments; + + return Scaffold( + backgroundColor: VoximplantColors.primaryDark, + body: SafeArea( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + child: Column( + children: [ + Widgets.textWithPadding( + text: 'Call failed', + textColor: VoximplantColors.white, + fontSize: 40, + ), + Widgets.textWithPadding( + text: '${_arguments.endpoint}', + textColor: VoximplantColors.white, + fontSize: 30, + verticalPadding: 30, + ), + Widgets.textWithPadding( + text: '${_arguments.failureReason}', + textColor: VoximplantColors.white, + fontSize: 25, + ), + ], + ), + ), + Widgets.iconButton( + icon: Icons.close, + color: VoximplantColors.button, + tooltip: 'Close', + onPressed: () { + Navigator.of(context) + .pushReplacementNamed(AppRoutes.makeCall); + }), + ], + ), + ), + ), + ); + } +} diff --git a/video_call/lib/call_failed/call_failed_page_arguments.dart b/video_call/lib/call_failed/call_failed_page_arguments.dart new file mode 100644 index 0000000..3dc9e06 --- /dev/null +++ b/video_call/lib/call_failed/call_failed_page_arguments.dart @@ -0,0 +1,11 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:meta/meta.dart'; + +class CallFailedPageArguments { + final String failureReason; + final String endpoint; + + CallFailedPageArguments( + {@required this.failureReason, @required this.endpoint}); +} diff --git a/video_call/lib/incoming_call/bloc/bloc.dart b/video_call/lib/incoming_call/bloc/bloc.dart new file mode 100644 index 0000000..b975be7 --- /dev/null +++ b/video_call/lib/incoming_call/bloc/bloc.dart @@ -0,0 +1,5 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'incoming_call_bloc.dart'; +export 'incoming_call_event.dart'; +export 'incoming_call_state.dart'; diff --git a/video_call/lib/incoming_call/bloc/incoming_call_bloc.dart b/video_call/lib/incoming_call/bloc/incoming_call_bloc.dart new file mode 100644 index 0000000..4e3ff9b --- /dev/null +++ b/video_call/lib/incoming_call/bloc/incoming_call_bloc.dart @@ -0,0 +1,93 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'dart:async'; +import 'dart:io'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:video_call/services/auth_service.dart'; +import 'package:video_call/services/call_service.dart'; +import 'package:video_call/services/call_state.dart'; + +import 'incoming_call_event.dart'; +import 'incoming_call_state.dart'; + +class IncomingCallBloc extends Bloc { + final AuthService authService; + final CallService callService; + + String _callId; + StreamSubscription _callStateSubscription; + + IncomingCallBloc({@required this.authService, this.callService}); + + @override + IncomingCallState get initialState => IncomingCallInitial(); + + @override + Stream mapEventToState(IncomingCallEvent event) async* { + if (event is Load) { + _callId = callService.activeCallId; + _callStateSubscription = callService + .subscribeToCallStateChanges(_callId) + .listen(onCallStateChanged); + } + if (event is CheckPermissions) { + yield* _checkPermissions(); + } + if (event is DeclineCall) { + await callService.declineCall(_callId); + } + if (event is IncomingCallDisconnected) { + yield CallHasEnded(); + } + } + + @override + Future close() { + if (_callStateSubscription != null) { + _callStateSubscription.cancel(); + } + return super.close(); + } + + Stream _checkPermissions() async* { + if (Platform.isAndroid) { + PermissionStatus recordAudio = await PermissionHandler() + .checkPermissionStatus(PermissionGroup.microphone); + PermissionStatus camera = await PermissionHandler() + .checkPermissionStatus(PermissionGroup.camera); + List requestPermissions = List(); + if (recordAudio != PermissionStatus.granted) { + requestPermissions.add(PermissionGroup.microphone); + } + if (camera != PermissionStatus.granted) { + requestPermissions.add(PermissionGroup.camera); + } + if (requestPermissions.isEmpty) { + yield PermissionCheckPass(); + } else { + Map result = + await PermissionHandler().requestPermissions(requestPermissions); + if (result[PermissionGroup.microphone] != PermissionStatus.granted || + result[PermissionGroup.camera] != PermissionStatus.granted) { + yield PermissionCheckFailed(); + } else { + yield PermissionCheckPass(); + } + } + } else if (Platform.isIOS) { + yield PermissionCheckPass(); + } else { + //not supported platforms + yield PermissionCheckFailed(); + } + } + + void onCallStateChanged(CallState state) { + if (state is CallStateDisconnected) { + add(IncomingCallDisconnected()); + } + } +} diff --git a/video_call/lib/incoming_call/bloc/incoming_call_event.dart b/video_call/lib/incoming_call/bloc/incoming_call_event.dart new file mode 100644 index 0000000..891d027 --- /dev/null +++ b/video_call/lib/incoming_call/bloc/incoming_call_event.dart @@ -0,0 +1,18 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:equatable/equatable.dart'; + +abstract class IncomingCallEvent extends Equatable { + const IncomingCallEvent(); + + @override + List get props => []; +} + +class Load extends IncomingCallEvent {} + +class CheckPermissions extends IncomingCallEvent {} + +class DeclineCall extends IncomingCallEvent {} + +class IncomingCallDisconnected extends IncomingCallEvent {} diff --git a/video_call/lib/incoming_call/bloc/incoming_call_state.dart b/video_call/lib/incoming_call/bloc/incoming_call_state.dart new file mode 100644 index 0000000..c0a1744 --- /dev/null +++ b/video_call/lib/incoming_call/bloc/incoming_call_state.dart @@ -0,0 +1,18 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:equatable/equatable.dart'; + +abstract class IncomingCallState extends Equatable { + const IncomingCallState(); + + @override + List get props => []; +} + +class IncomingCallInitial extends IncomingCallState {} + +class PermissionCheckFailed extends IncomingCallState {} + +class PermissionCheckPass extends IncomingCallState {} + +class CallHasEnded extends IncomingCallState {} diff --git a/video_call/lib/incoming_call/incoming_call.dart b/video_call/lib/incoming_call/incoming_call.dart new file mode 100644 index 0000000..a35d6b9 --- /dev/null +++ b/video_call/lib/incoming_call/incoming_call.dart @@ -0,0 +1,5 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'incoming_call_page.dart'; +export 'incoming_call_page_arguments.dart'; +export 'bloc/bloc.dart'; diff --git a/video_call/lib/incoming_call/incoming_call_page.dart b/video_call/lib/incoming_call/incoming_call_page.dart new file mode 100644 index 0000000..943a760 --- /dev/null +++ b/video_call/lib/incoming_call/incoming_call_page.dart @@ -0,0 +1,92 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:video_call/active_call/active_call.dart'; +import 'package:video_call/incoming_call/incoming_call.dart'; +import 'package:video_call/routes.dart'; +import 'package:video_call/theme/voximplant_theme.dart'; +import 'package:video_call/widgets/widgets.dart'; + +import 'bloc/incoming_call_bloc.dart'; + +class IncomingCallPage extends StatefulWidget { + @override + State createState() { + return _IncomingCallPageState(); + } +} + +class _IncomingCallPageState extends State { + void _answerCall() { + BlocProvider.of(context).add(CheckPermissions()); + } + + void _declineCall() { + BlocProvider.of(context).add(DeclineCall()); + } + + @override + Widget build(BuildContext context) { + final IncomingCallPageArguments _arguments = + ModalRoute.of(context).settings.arguments; + + return BlocListener( + listener: (context, state) { + if (state is CallHasEnded) { + Navigator.of(context).pushReplacementNamed(AppRoutes.makeCall); + } + if (state is PermissionCheckPass) { + Navigator.of(context).pushReplacementNamed( + AppRoutes.activeCall, + arguments: ActiveCallPageArguments(isIncoming: true), + ); + } + }, + child: BlocBuilder( + builder: (context, state) { + return Scaffold( + backgroundColor: VoximplantColors.primaryDark, + body: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Widgets.textWithPadding( + text: 'Incoming call from', + textColor: VoximplantColors.white, + fontSize: 30, + verticalPadding: 20, + ), + Widgets.textWithPadding( + text: '${_arguments.caller}', + textColor: VoximplantColors.white, + fontSize: 25, + ), + Padding( + padding: EdgeInsets.only(top: 80), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Widgets.iconButton( + icon: Icons.call, + color: VoximplantColors.button, + tooltip: 'Answer', + onPressed: _answerCall), + Widgets.iconButton( + icon: Icons.call_end, + color: VoximplantColors.red, + tooltip: 'Decline', + onPressed: _declineCall) + ], + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/video_call/lib/incoming_call/incoming_call_page_arguments.dart b/video_call/lib/incoming_call/incoming_call_page_arguments.dart new file mode 100644 index 0000000..5dabf69 --- /dev/null +++ b/video_call/lib/incoming_call/incoming_call_page_arguments.dart @@ -0,0 +1,7 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +class IncomingCallPageArguments { + String caller; + + IncomingCallPageArguments({this.caller}); +} diff --git a/video_call/lib/login/bloc/bloc.dart b/video_call/lib/login/bloc/bloc.dart new file mode 100644 index 0000000..eabedf6 --- /dev/null +++ b/video_call/lib/login/bloc/bloc.dart @@ -0,0 +1,5 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'login_state.dart'; +export 'login_bloc.dart'; +export 'login_event.dart'; diff --git a/video_call/lib/login/bloc/login_bloc.dart b/video_call/lib/login/bloc/login_bloc.dart new file mode 100644 index 0000000..aaee587 --- /dev/null +++ b/video_call/lib/login/bloc/login_bloc.dart @@ -0,0 +1,45 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:video_call/login/login.dart'; +import 'package:video_call/services/auth_service.dart'; + +import 'package:flutter_voximplant/flutter_voximplant.dart'; + +class LoginBloc extends Bloc { + final AuthService authService; + + LoginBloc({@required this.authService}); + + @override + LoginState get initialState => LoginInitial(); + + @override + Stream mapEventToState(LoginEvent event) async* { + if (event is LoadLastUser) { + String lastUser = await authService.getUsername(); + yield LoginLastUserLoaded(lastUser: lastUser); + bool canUseAccessToken = await authService.canUseAccessToken(); + if (canUseAccessToken) { + yield LoginInProgress(); + try { + await authService.loginWithAccessToken(); + yield LoginSuccess(); + } on VIException catch (e) { + yield LoginFailure(errorCode: e.code, errorDescription: e.message); + } + } + } + if (event is LoginWithPassword) { + yield LoginInProgress(); + try { + await authService.loginWithPassword( + event.username + '.voximplant.com', event.password); + yield LoginSuccess(); + } on VIException catch (e) { + yield LoginFailure(errorCode: e.code, errorDescription: e.message); + } + } + } +} diff --git a/video_call/lib/login/bloc/login_event.dart b/video_call/lib/login/bloc/login_event.dart new file mode 100644 index 0000000..f38f713 --- /dev/null +++ b/video_call/lib/login/bloc/login_event.dart @@ -0,0 +1,26 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:meta/meta.dart'; +import 'package:equatable/equatable.dart'; + +abstract class LoginEvent extends Equatable { + const LoginEvent(); + @override + List get props => []; +} + +class LoadLastUser extends LoginEvent {} + +class LoginWithPassword extends LoginEvent { + final String username; + final String password; + + LoginWithPassword({@required this.username, @required this.password}); + + @override + List get props => [username, password]; + + @override + String toString() => 'LoginWithPassword: ' + 'username: $username, password: *****'; +} diff --git a/video_call/lib/login/bloc/login_state.dart b/video_call/lib/login/bloc/login_state.dart new file mode 100644 index 0000000..0eda659 --- /dev/null +++ b/video_call/lib/login/bloc/login_state.dart @@ -0,0 +1,43 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:meta/meta.dart'; +import 'package:equatable/equatable.dart'; + +abstract class LoginState extends Equatable { + const LoginState(); + + @override + List get props => []; +} + +class LoginInitial extends LoginState {} + +class LoginLastUserLoaded extends LoginState { + final String lastUser; + const LoginLastUserLoaded({@required this.lastUser}); + + @override + List get props => [lastUser]; + + @override + String toString() => 'LoginLastUserLoaded: user: $lastUser'; +} + +class LoginInProgress extends LoginState {} + +class LoginSuccess extends LoginState {} + +class LoginFailure extends LoginState { + final String errorCode; + final String errorDescription; + + const LoginFailure( + {@required this.errorCode, @required this.errorDescription}); + + @override + List get props => [errorCode, errorDescription]; + + @override + String toString() => + 'LoginStateFailure: errorCode: $errorCode, errorDescription: $errorDescription'; +} diff --git a/video_call/lib/login/login.dart b/video_call/lib/login/login.dart new file mode 100644 index 0000000..267869a --- /dev/null +++ b/video_call/lib/login/login.dart @@ -0,0 +1,5 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'login_form.dart'; +export 'login_page.dart'; +export 'bloc/bloc.dart'; diff --git a/video_call/lib/login/login_form.dart b/video_call/lib/login/login_form.dart new file mode 100644 index 0000000..d3212b7 --- /dev/null +++ b/video_call/lib/login/login_form.dart @@ -0,0 +1,139 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:video_call/login/login.dart'; +import 'package:video_call/routes.dart'; +import 'package:video_call/theme/voximplant_theme.dart'; +import 'package:video_call/widgets/widgets.dart'; + +class LoginForm extends StatefulWidget { + @override + State createState() { + return _LoginFormState(); + } +} + +class _LoginFormState extends State { + final _usernameController = TextEditingController(); + final _passwordController = TextEditingController(); + final _formKey = GlobalKey(); + + bool _isUsernameValid = true; + bool _isPasswordValid = true; + + @override + void initState() { + super.initState(); + BlocProvider.of(context).add(LoadLastUser()); + } + + @override + void dispose() { + _usernameController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + void _login() { + BlocProvider.of(context).add(LoginWithPassword( + username: _usernameController.text, + password: _passwordController.text)); + } + + void _handleLoginFailed(String errorCode, String errorDescription) { + if (errorCode == 'ERROR_INVALID_USERNAME') { + setState(() { + _isUsernameValid = false; + }); + } else if (errorCode == 'ERROR_INVALID_PASSWORD') { + setState(() { + _isPasswordValid = false; + }); + } else { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('$errorDescription'), + backgroundColor: Colors.red, + )); + } + } + + Widget _loginForm() { + return Center( + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Widgets.textWithPadding( + text: 'Video call', + fontSize: 30, + textColor: VoximplantColors.white, + verticalPadding: 30, + ), + Widgets.textFormField( + controller: _usernameController, + darkBackground: true, + labelText: 'user@app.account', + suffixText: '.voximplant.com', + inputType: TextInputType.emailAddress, + validator: (_) { + return _isUsernameValid ? null : 'Invalid username'; + }), + Widgets.textFormField( + controller: _passwordController, + darkBackground: true, + labelText: 'password', + obscureText: true, + validator: (_) { + return _isPasswordValid ? null : 'Invalid password'; + }), + Widgets.maxWidthRaisedButton( + text: 'Log in', + onPressed: _login, + ), + ], + ))); + } + + Widget _loginInProgress() { + return Center( + child: CircularProgressIndicator(), + ); + } + + return BlocListener( + listener: (context, state) { + if (state is LoginLastUserLoaded) { + _usernameController.text = state.lastUser; + } + if (state is LoginFailure) { + _handleLoginFailed(state.errorCode, state.errorDescription); + } + if (state is LoginSuccess) { + setState(() { + _isUsernameValid = true; + _isPasswordValid = true; + }); + Navigator.of(context).pushReplacementNamed(AppRoutes.makeCall); + } + if (state is! LoginInProgress && state is! LoginSuccess) { + Future.delayed(Duration(milliseconds: 100), + () => _formKey?.currentState?.validate()); + } + }, + child: BlocBuilder( + builder: (context, state) { + if (state is LoginInProgress || state is LoginSuccess) { + return _loginInProgress(); + } else { + return _loginForm(); + } + }, + ), + ); + } +} diff --git a/video_call/lib/login/login_page.dart b/video_call/lib/login/login_page.dart new file mode 100644 index 0000000..135f57a --- /dev/null +++ b/video_call/lib/login/login_page.dart @@ -0,0 +1,17 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter/material.dart'; +import 'package:video_call/login/login.dart'; +import 'package:video_call/theme/voximplant_theme.dart'; + +class LoginPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: VoximplantColors.primary, + body: SafeArea( + child: LoginForm(), + ), + ); + } +} diff --git a/video_call/lib/main.dart b/video_call/lib/main.dart new file mode 100644 index 0000000..85f37da --- /dev/null +++ b/video_call/lib/main.dart @@ -0,0 +1,99 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_voximplant/flutter_voximplant.dart'; +import 'package:video_call/active_call/active_call.dart'; +import 'package:video_call/incoming_call/incoming_call.dart'; +import 'package:video_call/login/login.dart'; +import 'package:video_call/make_call/make_call.dart'; +import 'package:video_call/routes.dart'; +import 'package:video_call/services/auth_service.dart'; +import 'package:video_call/services/call_service.dart'; +import 'package:video_call/theme/voximplant_theme.dart'; + +import 'call_failed/call_failed_page.dart'; + +class SimpleBlocDelegate extends BlocDelegate { + @override + void onEvent(Bloc bloc, Object event) { + super.onEvent(bloc, event); + print(event); + } + + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + print(transition); + } + + @override + void onError(Bloc bloc, Object error, StackTrace stacktrace) { + super.onError(bloc, error, stacktrace); + print(error); + } +} + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + BlocSupervisor.delegate = SimpleBlocDelegate(); + + VIClientConfig clientConfig = VIClientConfig(); + VIClient client = Voximplant().getClient(clientConfig); + final AuthService authService = AuthService(client); + final CallService callService = CallService(client); + + runApp( + App(authService: authService, callService: callService), + ); +} + +class App extends StatelessWidget { + final AuthService authService; + final CallService callService; + + App({Key key, @required this.authService, @required this.callService}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData( + primaryColor: VoximplantColors.primary, + primaryColorDark: VoximplantColors.primaryDark, + accentColor: VoximplantColors.accent, + ), + initialRoute: AppRoutes.login, + routes: { + AppRoutes.login: (context) { + return BlocProvider( + create: (context) => LoginBloc(authService: authService), + child: LoginPage(), + ); + }, + AppRoutes.makeCall: (context) { + return BlocProvider( + create: (context) => MakeCallBloc( + authService: authService, callService: callService), + child: MakeCallPage(), + ); + }, + AppRoutes.activeCall: (context) { + return BlocProvider( + create: (context) => ActiveCallBloc( + authService: authService, callService: callService), + child: ActiveCallPage(), + ); + }, + AppRoutes.incomingCall: (context) { + return BlocProvider( + create: (context) => IncomingCallBloc( + authService: authService, callService: callService) + ..add(Load()), + child: IncomingCallPage(), + ); + }, + AppRoutes.callFailed: (context) => CallFailedPage(), + }); + } +} diff --git a/video_call/lib/make_call/bloc/bloc.dart b/video_call/lib/make_call/bloc/bloc.dart new file mode 100644 index 0000000..fb59f32 --- /dev/null +++ b/video_call/lib/make_call/bloc/bloc.dart @@ -0,0 +1,5 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'make_call_bloc.dart'; +export 'make_call_event.dart'; +export 'make_call_state.dart'; diff --git a/video_call/lib/make_call/bloc/make_call_bloc.dart b/video_call/lib/make_call/bloc/make_call_bloc.dart new file mode 100644 index 0000000..414c00b --- /dev/null +++ b/video_call/lib/make_call/bloc/make_call_bloc.dart @@ -0,0 +1,92 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'dart:io'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter_voximplant/flutter_voximplant.dart'; +import 'package:meta/meta.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:video_call/make_call/make_call.dart'; +import 'package:video_call/services/auth_service.dart'; +import 'package:video_call/services/call_service.dart'; + +class MakeCallBloc extends Bloc { + final AuthService authService; + final CallService callService; + + MakeCallBloc({@required this.authService, @required this.callService}) { + callService.onIncomingCall = onIncomingCall; + authService.onDisconnected = onConnectionClosed; + } + + @override + MakeCallState get initialState => + MakeCallInitial(displayName: authService.displayName); + + void onIncomingCall(String callId, String caller) { + add(ReceivedIncomingCall(callId: callId, caller: caller)); + } + + void onConnectionClosed() { + add(ConnectionClosed()); + } + + Stream _checkPermissions() async* { + if (Platform.isAndroid) { + PermissionStatus recordAudio = await PermissionHandler() + .checkPermissionStatus(PermissionGroup.microphone); + PermissionStatus camera = await PermissionHandler() + .checkPermissionStatus(PermissionGroup.camera); + List requestPermissions = List(); + if (recordAudio != PermissionStatus.granted) { + requestPermissions.add(PermissionGroup.microphone); + } + if (camera != PermissionStatus.granted) { + requestPermissions.add(PermissionGroup.camera); + } + if (requestPermissions.isEmpty) { + yield PermissionCheckSuccess(displayName: authService.displayName); + } else { + Map result = + await PermissionHandler().requestPermissions(requestPermissions); + if (result[PermissionGroup.microphone] != PermissionStatus.granted || + result[PermissionGroup.camera] != PermissionStatus.granted) { + yield PermissionCheckFail(displayName: authService.displayName); + } else { + yield PermissionCheckSuccess(displayName: authService.displayName); + } + } + } else if (Platform.isIOS) { + yield PermissionCheckSuccess(displayName: authService.displayName); + } else { + //not supported platforms + yield PermissionCheckFail(displayName: authService.displayName); + } + } + + @override + Stream mapEventToState(MakeCallEvent event) async* { + if (event is CheckPermissionsForCall) { + yield* _checkPermissions(); + } + if (event is LogOut) { + await authService.logout(); + yield LoggedOut(networkIssues: false); + } + if (event is ReceivedIncomingCall) { + yield IncomingCall(caller: event.caller); + } + if (event is ConnectionClosed) { + yield LoggedOut(networkIssues: true); + } + if (event is Reconnect) { + try { + await authService.loginWithAccessToken(); + yield ReconnectSuccess(displayName: authService.displayName); + } on VIException { + authService.onDisconnected = null; + yield ReconnectFailed(); + } + } + } +} diff --git a/video_call/lib/make_call/bloc/make_call_event.dart b/video_call/lib/make_call/bloc/make_call_event.dart new file mode 100644 index 0000000..fc21c10 --- /dev/null +++ b/video_call/lib/make_call/bloc/make_call_event.dart @@ -0,0 +1,29 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:meta/meta.dart'; +import 'package:equatable/equatable.dart'; + +abstract class MakeCallEvent extends Equatable { + const MakeCallEvent(); + + @override + List get props => []; +} + +class CheckPermissionsForCall extends MakeCallEvent {} + +class LogOut extends MakeCallEvent {} + +class ReceivedIncomingCall extends MakeCallEvent { + final String callId; + final String caller; + + ReceivedIncomingCall({@required this.callId, @required this.caller}); + + @override + List get props => [callId, caller]; +} + +class ConnectionClosed extends MakeCallEvent {} + +class Reconnect extends MakeCallEvent {} diff --git a/video_call/lib/make_call/bloc/make_call_state.dart b/video_call/lib/make_call/bloc/make_call_state.dart new file mode 100644 index 0000000..8c9ed25 --- /dev/null +++ b/video_call/lib/make_call/bloc/make_call_state.dart @@ -0,0 +1,48 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + +abstract class MakeCallState extends Equatable { + final String displayName; + + const MakeCallState(this.displayName); + + @override + List get props => [displayName]; +} + +class MakeCallInitial extends MakeCallState { + const MakeCallInitial({@required String displayName}) : super(displayName); + + @override + String toString() => 'OutgoingCallInitial: displayName: $displayName'; +} + +class PermissionCheckFail extends MakeCallState { + const PermissionCheckFail({@required String displayName}) + : super(displayName); +} + +class PermissionCheckSuccess extends MakeCallState { + const PermissionCheckSuccess({@required String displayName}) + : super(displayName); +} + +class LoggedOut extends MakeCallState { + final bool networkIssues; + const LoggedOut({@required this.networkIssues}) : super(null); +} + +class IncomingCall extends MakeCallState { + final String caller; + const IncomingCall({@required this.caller}) : super(null); +} + +class ReconnectSuccess extends MakeCallState { + const ReconnectSuccess({@required String displayName}) : super(displayName); +} + +class ReconnectFailed extends MakeCallState { + const ReconnectFailed() : super(null); +} diff --git a/video_call/lib/make_call/make_call.dart b/video_call/lib/make_call/make_call.dart new file mode 100644 index 0000000..1458446 --- /dev/null +++ b/video_call/lib/make_call/make_call.dart @@ -0,0 +1,4 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +export 'make_call_page.dart'; +export 'bloc/bloc.dart'; diff --git a/video_call/lib/make_call/make_call_page.dart b/video_call/lib/make_call/make_call_page.dart new file mode 100644 index 0000000..ee34733 --- /dev/null +++ b/video_call/lib/make_call/make_call_page.dart @@ -0,0 +1,142 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:video_call/active_call/active_call.dart'; +import 'package:video_call/incoming_call/incoming_call.dart'; +import 'package:video_call/routes.dart'; +import 'package:video_call/widgets/widgets.dart'; + +import 'bloc/make_call_bloc.dart'; +import 'bloc/make_call_event.dart'; +import 'bloc/make_call_state.dart'; + +class MakeCallPage extends StatefulWidget { + MakeCallPage({Key key}) : super(key: key); + + @override + State createState() { + return _MakeCallPageState(); + } +} + +class _MakeCallPageState extends State { + final _callToController = TextEditingController(); + + @override + void dispose() { + _callToController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + void _makeVideoCall() { + BlocProvider.of(context).add(CheckPermissionsForCall()); + } + + void _logout() { + BlocProvider.of(context).add(LogOut()); + } + + void _showPermissionCheckError() { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('Permissions missing'), + content: Text( + 'Please give "record audio" and "camera" permissions to make calls'), + actions: [ + FlatButton( + child: Text('Close'), + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + ); + }, + ); + } + + return BlocListener( + listener: (context, state) { + if (state is LoggedOut) { + if (state.networkIssues) { + BlocProvider.of(context).add(Reconnect()); + } else { + Navigator.of(context).pushReplacementNamed(AppRoutes.login); + } + } + if (state is PermissionCheckSuccess) { + Navigator.of(context).pushReplacementNamed( + AppRoutes.activeCall, + arguments: ActiveCallPageArguments( + isIncoming: false, callTo: _callToController.text), + ); + } + if (state is PermissionCheckFailed) { + _showPermissionCheckError(); + } + if (state is IncomingCall) { + Navigator.of(context).pushReplacementNamed( + AppRoutes.incomingCall, + arguments: IncomingCallPageArguments(caller: state.caller), + ); + } + if (state is ReconnectFailed) { + Navigator.of(context).pushReplacementNamed(AppRoutes.login); + } + }, + child: BlocBuilder( + builder: (context, state) { + return Scaffold( + appBar: AppBar( + title: Text('Voximplant'), + actions: [ + IconButton( + icon: Icon(Icons.exit_to_app), + onPressed: _logout, + ) + ], + ), + body: SafeArea( + child: Stack( + children: [ + Center( + child: Form( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Widgets.textFormField( + controller: _callToController, + darkBackground: false, + labelText: 'user or number'), + Widgets.maxWidthRaisedButton( + text: 'Video call', + onPressed: _makeVideoCall, + ), + ], + ), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: EdgeInsets.only(bottom: 20), + child: Text( + 'Logged in as ${state.displayName}', + ), + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/video_call/lib/routes.dart b/video_call/lib/routes.dart new file mode 100644 index 0000000..26771a8 --- /dev/null +++ b/video_call/lib/routes.dart @@ -0,0 +1,9 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +class AppRoutes { + static final String login = '/login'; + static final String makeCall = '/makeCall'; + static final String incomingCall = '/incomingCall'; + static final String activeCall = '/activeCall'; + static final String callFailed = '/callFailed'; +} diff --git a/video_call/lib/services/auth_service.dart b/video_call/lib/services/auth_service.dart new file mode 100644 index 0000000..f6d6984 --- /dev/null +++ b/video_call/lib/services/auth_service.dart @@ -0,0 +1,95 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter_voximplant/flutter_voximplant.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +typedef void Disconnected(); + +class AuthService { + VIClient _client; + String _displayName; + + String get displayName => _displayName; + Disconnected onDisconnected; + + AuthService(VIClient client) { + _client = client; + _client.clientStateStream.listen((state) { + print('AuthService: client state is changed: $state'); + if (state == VIClientState.Disconnected && onDisconnected != null) { + onDisconnected(); + } + }); + } + + Future loginWithPassword(String username, String password) async { + print('AuthService: loginWithPassword'); + VIClientState clientState = await _client.getClientState(); + if (clientState == VIClientState.LoggedIn) { + return _displayName; + } + if (clientState == VIClientState.Disconnected) { + await _client.connect(); + } + VIAuthResult authResult = await _client.login(username, password); + await _saveAuthDetails(username, authResult.loginTokens); + _displayName = authResult.displayName; + return _displayName; + } + + Future loginWithAccessToken([String username]) async { + print('AuthService: loginWithAccessToken'); + VIClientState clientState = await _client.getClientState(); + if (clientState == VIClientState.LoggedIn) { + return _displayName; + } + if (clientState == VIClientState.Disconnected) { + await _client.connect(); + } + SharedPreferences prefs = await SharedPreferences.getInstance(); + VILoginTokens loginTokens = _getAuthDetails(prefs); + String user = username ?? prefs.getString('username'); + + VIAuthResult authResult = + await _client.loginWithAccessToken(user, loginTokens.accessToken); + await _saveAuthDetails(user, authResult.loginTokens); + _displayName = authResult.displayName; + return _displayName; + } + + Future logout() async { + await _client.disconnect(); + VILoginTokens loginTokens = VILoginTokens(); + _saveAuthDetails(null, loginTokens); + } + + Future getUsername() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.getString('username')?.replaceAll('.voximplant.com', ''); + } + + Future canUseAccessToken() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.getString('accessToken') != null; + } + + Future _saveAuthDetails( + String username, VILoginTokens loginTokens) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString('username', username); + prefs.setString('accessToken', loginTokens.accessToken); + prefs.setString('refreshToken', loginTokens.refreshToken); + prefs.setInt('accessExpire', loginTokens.accessExpire); + prefs.setInt('refreshExpire', loginTokens.refreshExpire); + } + + VILoginTokens _getAuthDetails(SharedPreferences prefs) { + VILoginTokens loginTokens = VILoginTokens(); + loginTokens.accessToken = prefs.getString('accessToken'); + loginTokens.accessExpire = prefs.getInt('accessExpire'); + loginTokens.refreshExpire = prefs.getInt('refreshExpire'); + loginTokens.refreshToken = prefs.getString('refreshToken'); + + return loginTokens; + } +} diff --git a/video_call/lib/services/call_service.dart b/video_call/lib/services/call_service.dart new file mode 100644 index 0000000..5c62266 --- /dev/null +++ b/video_call/lib/services/call_service.dart @@ -0,0 +1,189 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:flutter_voximplant/flutter_voximplant.dart'; + +import 'call_state.dart'; + +typedef void OnIncomingCall(String callId, String caller); + +class CallService { + final VIClient _client; + VICall _activeCall; + StreamController _activeStreamController; + OnIncomingCall onIncomingCall; + + String get activeCallId => _activeCall?.callId; + + CallService(this._client) { + _client.onIncomingCall = _onIncomingCall; + } + + Stream subscribeToCallStateChanges(String callId) { + if (_activeCall.callId == callId) { + _activeStreamController?.close(); + _activeStreamController = StreamController.broadcast(); + return _activeStreamController.stream; + } + return null; + } + + Future makeVideoCall({@required String callTo}) async { + if (_activeCall != null) { + throw 'There is already an active call'; + } + VICallSettings callSettings = VICallSettings(); + callSettings.videoFlags = VIVideoFlags(receiveVideo: true, sendVideo: true); + callSettings.preferredVideoCodec = VIVideoCodec.VP8; + _activeCall = await _client.call(callTo, callSettings); + _listenToActiveCallEvents(); + return _activeCall.callId; + } + + Future answerVideoCall() async { + if (_activeCall == null) { + throw 'No active call'; + } + VICallSettings callSettings = VICallSettings(); + callSettings.videoFlags = VIVideoFlags(receiveVideo: true, sendVideo: true); + callSettings.preferredVideoCodec = VIVideoCodec.VP8; + await _activeCall.answer(callSettings); + return _activeCall.callId; + } + + Future endCall(String callId) async { + if (_activeCall.callId == callId) { + await _activeCall.hangup(); + } + } + + Future declineCall(String callId) async { + if (_activeCall.callId == callId) { + await _activeCall.decline(); + } + } + + Future holdCall(String callId, bool doHold) async { + if (_activeCall.callId == callId) { + return _activeCall.hold(doHold); + } else { + throw 'No active call'; + } + } + + Future sendVideo(String callId, bool doSendVideo) async { + if (_activeCall.callId == callId) { + await _activeCall.sendVideo(doSendVideo); + } else { + throw 'No active call'; + } + } + + void _onIncomingCall(VIClient client, VICall call, bool video, + Map headers) async { + if (_activeCall != null) { + await call.decline(); + return; + } + if (onIncomingCall != null) { + _activeCall = call; + _listenToActiveCallEvents(); + onIncomingCall( + _activeCall.callId, _activeCall.endpoints?.first?.displayName); + } + } + + void _listenToActiveCallEvents() { + _activeCall.onCallRinging = _onCallRinging; + _activeCall.onCallConnected = _onCallConnected; + _activeCall.onCallDisconnected = _onCallDisconnected; + _activeCall.onCallFailed = _onCallFailed; + _activeCall.onLocalVideoStreamAdded = _onLocalVideoStreamAdded; + _activeCall.onLocalVideoStreamRemoved = _onLocalVideoStreamRemoved; + _activeCall.onEndpointAdded = _onEndpointAdded; + } + + void _listenToEndpointEvents() { + _activeCall.endpoints?.first?.onRemoteVideoStreamAdded = + _onRemoteVideoStreamAdded; + _activeCall.endpoints?.first?.onRemoteVideoStreamRemoved = + _onRemoteVideoStreamRemoved; + } + + void _onCallDisconnected( + VICall call, Map headers, bool answeredElsewhere) { + if (call.callId == _activeCall.callId) { + print('CallService: onCallDisconnected($headers, $answeredElsewhere)'); + _activeStreamController.add(CallStateDisconnected()); + _activeCall = null; + } + } + + void _onCallFailed( + VICall call, int code, String description, Map headers) { + if (call.callId == _activeCall.callId) { + print('CallService: onCallFailed($code, $description, $headers)'); + _activeStreamController.add(CallStateFailed( + errorDescription: description, + endpoint: _activeCall.endpoints?.first?.displayName)); + _activeCall = null; + } + } + + void _onCallConnected(VICall call, Map headers) { + if (call.callId == _activeCall.callId) { + print('CallService: onCallConnected($headers)'); + _activeStreamController.add(CallStateConnected()); + } + } + + void _onCallRinging(VICall call, Map headers) { + if (call.callId == _activeCall.callId) { + print('CallService: onCallRinging($headers)'); + _activeStreamController.add(CallStateRinging()); + } + } + + void _onEndpointAdded(VICall call, VIEndpoint endpoint) { + if (call.callId == _activeCall.callId) { + print('CallService: onEndpointAdded($endpoint)'); + _listenToEndpointEvents(); + } + } + + void _onLocalVideoStreamAdded(VICall call, VIVideoStream videoStream) { + if (call.callId == _activeCall.callId) { + print( + 'CallService: onLocalVideoStreamAdded: ${videoStream.streamId}'); + _activeStreamController.add(CallStateVideoStreamAdded( + streamId: videoStream.streamId, isLocal: true)); + } + } + + void _onLocalVideoStreamRemoved(VICall call, VIVideoStream videoStream) { + if (call.callId == _activeCall.callId) { + print( + 'CallService: onLocalVideoStreamRemoved: ${videoStream.streamId}'); + _activeStreamController.add(CallStateVideoStreamRemoved( + streamId: videoStream.streamId, isLocal: true)); + } + } + + void _onRemoteVideoStreamAdded( + VIEndpoint endpoint, VIVideoStream videoStream) { + print( + 'CallService: onRemoteVideoStreamAdded: ${videoStream.streamId}'); + _activeStreamController.add(CallStateVideoStreamAdded( + streamId: videoStream.streamId, isLocal: false)); + } + + void _onRemoteVideoStreamRemoved( + VIEndpoint endpoint, VIVideoStream videoStream) { + print( + 'CallService: onRemoteVideoStreamRemoved: ${videoStream.streamId}'); + _activeStreamController.add(CallStateVideoStreamRemoved( + streamId: videoStream.streamId, isLocal: false)); + } +} diff --git a/video_call/lib/services/call_state.dart b/video_call/lib/services/call_state.dart new file mode 100644 index 0000000..b53fd25 --- /dev/null +++ b/video_call/lib/services/call_state.dart @@ -0,0 +1,33 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:meta/meta.dart'; + +abstract class CallState { + const CallState(); +} + +class CallStateRinging extends CallState {} + +class CallStateConnected extends CallState {} + +class CallStateFailed extends CallState { + final String errorDescription; + final String endpoint; + const CallStateFailed( + {@required this.errorDescription, @required this.endpoint}); +} + +class CallStateVideoStreamAdded extends CallState { + final String streamId; + final bool isLocal; + CallStateVideoStreamAdded({@required this.streamId, @required this.isLocal}); +} + +class CallStateVideoStreamRemoved extends CallState { + final String streamId; + final bool isLocal; + CallStateVideoStreamRemoved( + {@required this.streamId, @required this.isLocal}); +} + +class CallStateDisconnected extends CallState {} diff --git a/video_call/lib/theme/voximplant_theme.dart b/video_call/lib/theme/voximplant_theme.dart new file mode 100644 index 0000000..3623279 --- /dev/null +++ b/video_call/lib/theme/voximplant_theme.dart @@ -0,0 +1,13 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter/material.dart'; + +class VoximplantColors { + static const Color primary = const Color(0xff1c0b43); + static const Color primaryDark = const Color(0xff392b5b); + static const Color accent = const Color(0xff8b61ff); + static const Color button = const Color(0xff662eff); + static const Color white = const Color(0xffffffff); + static const Color red = const Color(0xfff54b5e); + static const Color grey = const Color(0xff212121); +} diff --git a/video_call/lib/widgets/widgets.dart b/video_call/lib/widgets/widgets.dart new file mode 100644 index 0000000..3014f38 --- /dev/null +++ b/video_call/lib/widgets/widgets.dart @@ -0,0 +1,106 @@ +/// Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + +import 'package:flutter/material.dart'; +import 'package:video_call/theme/voximplant_theme.dart'; + +class Widgets { + static Widget textFormField( + {@required TextEditingController controller, + @required bool darkBackground, + String labelText, + String suffixText, + bool obscureText = false, + TextInputType inputType = TextInputType.text, + FormFieldValidator validator}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + child: Theme( + data: ThemeData( + primaryColor: + darkBackground ? VoximplantColors.white : VoximplantColors.button, + cursorColor: + darkBackground ? VoximplantColors.white : VoximplantColors.button, + hintColor: + darkBackground ? VoximplantColors.white : VoximplantColors.button, + ), + child: TextFormField( + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide( + color: darkBackground + ? VoximplantColors.white + : VoximplantColors.button)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: darkBackground + ? VoximplantColors.white + : VoximplantColors.button)), + labelText: labelText, + suffixText: suffixText), + keyboardType: inputType, + controller: controller, + autocorrect: false, + obscureText: obscureText, + style: + TextStyle(color: darkBackground ? VoximplantColors.white : null), + validator: validator, + ), + ), + ); + } + + static Widget maxWidthRaisedButton( + {@required String text, @required VoidCallback onPressed}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + child: SizedBox( + width: double.infinity, + height: 50, + child: RaisedButton( + textColor: VoximplantColors.white, + color: VoximplantColors.button, + onPressed: onPressed, + child: Text(text), + ), + ), + ); + } + + static Widget textWithPadding( + {@required String text, + Color textColor, + double fontSize, + double verticalPadding = 0.0, + double horizontalPadding = 0.0}) { + return Padding( + padding: EdgeInsets.symmetric( + vertical: verticalPadding, horizontal: horizontalPadding), + child: Text( + text, + style: TextStyle(color: textColor, fontSize: fontSize), + ), + ); + } + + static Widget iconButton( + {@required IconData icon, + @required Color color, + @required String tooltip, + @required VoidCallback onPressed}) { + return Ink( + decoration: ShapeDecoration( + color: VoximplantColors.white, + shape: CircleBorder(), + ), + child: IconButton( + onPressed: onPressed, + iconSize: 40, + icon: Icon( + icon, + color: color, + ), + tooltip: tooltip, + ), + ); + } +} diff --git a/video_call/pubspec.lock b/video_call/pubspec.lock new file mode 100644 index 0000000..adebd03 --- /dev/null +++ b/video_call/pubspec.lock @@ -0,0 +1,285 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + bloc: + dependency: "direct main" + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_voximplant: + dependency: "direct main" + description: + name: flutter_voximplant + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.6" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0+1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.23.1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.6" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+3" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2+2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.11" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" +sdks: + dart: ">=2.6.0 <3.0.0" + flutter: ">=1.12.13+hotfix.4 <2.0.0" diff --git a/video_call/pubspec.yaml b/video_call/pubspec.yaml new file mode 100644 index 0000000..0297f24 --- /dev/null +++ b/video_call/pubspec.yaml @@ -0,0 +1,39 @@ +name: video_call +description: A new Flutter project. + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + bloc: 3.0.0 + flutter_bloc: 3.1.0 + equatable: 1.0.2 + flutter_voximplant: 2.0.0 + shared_preferences: 0.5.6 + permission_handler: 3.2.0 + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^0.1.2 + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + diff --git a/video_call/test/widget_test.dart b/video_call/test/widget_test.dart new file mode 100644 index 0000000..7a91322 --- /dev/null +++ b/video_call/test/widget_test.dart @@ -0,0 +1,35 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_voximplant/flutter_voximplant.dart'; + +import 'package:video_call/main.dart'; +import 'package:video_call/services/auth_service.dart'; +import 'package:video_call/services/call_service.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + VIClient client = Voximplant().getClient(); + await tester.pumpWidget(App( + authService: AuthService(client), callService: CallService(client))); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}