Codying style: naming, formatting, comments

This commit is contained in:
Lyubo Marinov 2018-05-03 17:53:38 -05:00
parent e5309a6482
commit 5e79bbecef
12 changed files with 322 additions and 316 deletions

View File

@ -110,7 +110,7 @@ public class AddPeopleController {
if (owner != null) { if (owner != null) {
WritableArray invitees = new WritableNativeArray(); WritableArray invitees = new WritableNativeArray();
for(int i = 0, size = ids.size(); i < size; i++) { for(int i = 0, size = ids.size(); i < size; i++) {
String id = ids.get(i); String id = ids.get(i);
@ -123,7 +123,7 @@ public class AddPeopleController {
// so just skip it. // so just skip it.
} }
} }
owner.invite(this, invitees); owner.invite(this, invitees);
} }
} }

View File

@ -79,11 +79,11 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C6F99C3A204DE6BE0001F710 /* AppDelegate.swift */, C6F99C3A204DE6BE0001F710 /* AppDelegate.swift */,
C6F99C3C204DE6BE0001F710 /* ViewController.swift */,
C6F99C3E204DE6BE0001F710 /* Main.storyboard */,
C6F99C41204DE6BE0001F710 /* Assets.xcassets */, C6F99C41204DE6BE0001F710 /* Assets.xcassets */,
C6F99C43204DE6BE0001F710 /* LaunchScreen.storyboard */,
C6F99C46204DE6BE0001F710 /* Info.plist */, C6F99C46204DE6BE0001F710 /* Info.plist */,
C6F99C43204DE6BE0001F710 /* LaunchScreen.storyboard */,
C6F99C3E204DE6BE0001F710 /* Main.storyboard */,
C6F99C3C204DE6BE0001F710 /* ViewController.swift */,
); );
path = src; path = src;
sourceTree = "<group>"; sourceTree = "<group>";

View File

@ -20,21 +20,21 @@ import JitsiMeet
class AppDelegate: UIResponder, UIApplicationDelegate { class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var window: UIWindow?
func application(_ application: UIApplication, func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
guard let launchOptions = launchOptions else { return false } guard let launchOptions = launchOptions else { return false }
return JitsiMeetView.application(application, didFinishLaunchingWithOptions: launchOptions) return JitsiMeetView.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
// MARK: - Linking delegate methods // MARK: - Linking delegate methods
func application(_ application: UIApplication, func application(_ application: UIApplication,
continue userActivity: NSUserActivity, continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool { restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
return JitsiMeetView.application(application, continue: userActivity, restorationHandler: restorationHandler) return JitsiMeetView.application(application, continue: userActivity, restorationHandler: restorationHandler)
} }
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return JitsiMeetView.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation) return JitsiMeetView.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation)
} }

View File

@ -20,27 +20,27 @@ import JitsiMeet
class ViewController: UIViewController { class ViewController: UIViewController {
@IBOutlet weak var videoButton: UIButton? @IBOutlet weak var videoButton: UIButton?
fileprivate var pipViewCoordinator: PiPViewCoordinator? fileprivate var pipViewCoordinator: PiPViewCoordinator?
fileprivate var jitsiMeetView: JitsiMeetView? fileprivate var jitsiMeetView: JitsiMeetView?
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
} }
override func viewWillTransition(to size: CGSize, override func viewWillTransition(to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator) { with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator) super.viewWillTransition(to: size, with: coordinator)
let rect = CGRect(origin: CGPoint.zero, size: size) let rect = CGRect(origin: CGPoint.zero, size: size)
pipViewCoordinator?.resetBounds(bounds: rect) pipViewCoordinator?.resetBounds(bounds: rect)
} }
// MARK: - Actions // MARK: - Actions
@IBAction func openJitsiMeet(sender: Any?) { @IBAction func openJitsiMeet(sender: Any?) {
cleanUp() cleanUp()
// create and configure jitsimeet view // create and configure jitsimeet view
let jitsiMeetView = JitsiMeetView() let jitsiMeetView = JitsiMeetView()
jitsiMeetView.welcomePageEnabled = true jitsiMeetView.welcomePageEnabled = true
@ -48,18 +48,18 @@ class ViewController: UIViewController {
jitsiMeetView.load(nil) jitsiMeetView.load(nil)
jitsiMeetView.delegate = self jitsiMeetView.delegate = self
self.jitsiMeetView = jitsiMeetView self.jitsiMeetView = jitsiMeetView
// Enable jitsimeet view to be a view that can be displayed // Enable jitsimeet view to be a view that can be displayed
// on top of all the things, and let the coordinator to manage // on top of all the things, and let the coordinator to manage
// the view state and interactions // the view state and interactions
pipViewCoordinator = PiPViewCoordinator(withView: jitsiMeetView) pipViewCoordinator = PiPViewCoordinator(withView: jitsiMeetView)
pipViewCoordinator?.configureAsStickyView(withParentView: view) pipViewCoordinator?.configureAsStickyView(withParentView: view)
// animate in // animate in
jitsiMeetView.alpha = 0 jitsiMeetView.alpha = 0
pipViewCoordinator?.show() pipViewCoordinator?.show()
} }
fileprivate func cleanUp() { fileprivate func cleanUp() {
jitsiMeetView?.removeFromSuperview() jitsiMeetView?.removeFromSuperview()
jitsiMeetView = nil jitsiMeetView = nil
@ -68,21 +68,21 @@ class ViewController: UIViewController {
} }
extension ViewController: JitsiMeetViewDelegate { extension ViewController: JitsiMeetViewDelegate {
func conferenceFailed(_ data: [AnyHashable : Any]!) { func conferenceFailed(_ data: [AnyHashable : Any]!) {
hideJitsiMeetViewAndCleanUp() hideJitsiMeetViewAndCleanUp()
} }
func conferenceLeft(_ data: [AnyHashable : Any]!) { func conferenceLeft(_ data: [AnyHashable : Any]!) {
hideJitsiMeetViewAndCleanUp() hideJitsiMeetViewAndCleanUp()
} }
func enterPicture(inPicture data: [AnyHashable : Any]!) { func enterPicture(inPicture data: [AnyHashable : Any]!) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.pipViewCoordinator?.enterPictureInPicture() self.pipViewCoordinator?.enterPictureInPicture()
} }
} }
private func hideJitsiMeetViewAndCleanUp() { private func hideJitsiMeetViewAndCleanUp() {
DispatchQueue.main.async { DispatchQueue.main.async {
self.pipViewCoordinator?.hide() { _ in self.pipViewCoordinator?.hide() { _ in

View File

@ -38,9 +38,9 @@
B386B85D20981A75000DEF7A /* Invite.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85620981A75000DEF7A /* Invite.m */; }; B386B85D20981A75000DEF7A /* Invite.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85620981A75000DEF7A /* Invite.m */; };
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; }; C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; }; C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
C69EFA0C209A0F660027712B /* JMCallKitNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA09209A0F650027712B /* JMCallKitNotifier.swift */; }; C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */; };
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */; }; C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */; };
C69EFA0E209A0F660027712B /* JMCallKitEventListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitEventListener.swift */; }; C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; };
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; }; C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; }; C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; }; C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; };
@ -86,9 +86,9 @@
B386B85620981A75000DEF7A /* Invite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Invite.m; sourceTree = "<group>"; }; B386B85620981A75000DEF7A /* Invite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Invite.m; sourceTree = "<group>"; };
C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; }; C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; }; C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
C69EFA09209A0F650027712B /* JMCallKitNotifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitNotifier.swift; sourceTree = "<group>"; }; C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitEmitter.swift; sourceTree = "<group>"; };
C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitProxy.swift; sourceTree = "<group>"; }; C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitProxy.swift; sourceTree = "<group>"; };
C69EFA0B209A0F660027712B /* JMCallKitEventListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitEventListener.swift; sourceTree = "<group>"; }; C69EFA0B209A0F660027712B /* JMCallKitListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitListener.swift; sourceTree = "<group>"; };
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; }; C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; }; C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; }; C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
@ -112,12 +112,12 @@
0BCA49681EC4BBE500B793EE /* Resources */ = { 0BCA49681EC4BBE500B793EE /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
75635B0820751D6D00F29C9F /* joined.wav */,
75635B0920751D6D00F29C9F /* left.wav */,
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */, 0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
C6245F5B2053091D0040BE68 /* image-resize@2x.png */, C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
C6245F5C2053091D0040BE68 /* image-resize@3x.png */, C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
0BCA496B1EC4BBF900B793EE /* jitsi.ttf */, 0BCA496B1EC4BBF900B793EE /* jitsi.ttf */,
75635B0820751D6D00F29C9F /* joined.wav */,
75635B0920751D6D00F29C9F /* left.wav */,
); );
name = Resources; name = Resources;
sourceTree = "<group>"; sourceTree = "<group>";
@ -144,25 +144,24 @@
0BD906E71EC0C00300C8C18E /* src */ = { 0BD906E71EC0C00300C8C18E /* src */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C69EFA02209A0EFD0027712B /* callkit */,
B386B84F20981A11000DEF7A /* invite */,
C6A3426B204F127900E062DD /* picture-in-picture */,
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
0BB9AD7C1F60356D001C08DB /* AppInfo.m */, 0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */, 0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
C69EFA02209A0EFD0027712B /* callkit */,
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */, 0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
0BD906E91EC0C00300C8C18E /* Info.plist */, 0BD906E91EC0C00300C8C18E /* Info.plist */,
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */, B386B84F20981A11000DEF7A /* invite */,
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */, 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */, 0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */, 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */, C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */, 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */, 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */,
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */, C6A3426B204F127900E062DD /* picture-in-picture */,
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */,
0BCA495D1EC4B6C600B793EE /* POSIX.m */, 0BCA495D1EC4B6C600B793EE /* POSIX.m */,
0BCA495E1EC4B6C600B793EE /* Proximity.m */, 0BCA495E1EC4B6C600B793EE /* Proximity.m */,
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */,
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */,
); );
path = src; path = src;
sourceTree = "<group>"; sourceTree = "<group>";
@ -170,11 +169,11 @@
9C3C6FA2341729836589B856 /* Frameworks */ = { 9C3C6FA2341729836589B856 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0BB9AD781F5EC6D7001C08DB /* Intents.framework */,
0BB9AD761F5EC6CE001C08DB /* CallKit.framework */, 0BB9AD761F5EC6CE001C08DB /* CallKit.framework */,
0B93EF7A1EC608550030D24D /* CoreText.framework */, 0B93EF7A1EC608550030D24D /* CoreText.framework */,
0BCA49631EC4B76D00B793EE /* WebRTC.framework */, 0BB9AD781F5EC6D7001C08DB /* Intents.framework */,
03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */, 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */,
0BCA49631EC4B76D00B793EE /* WebRTC.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -208,8 +207,9 @@
C69EFA02209A0EFD0027712B /* callkit */ = { C69EFA02209A0EFD0027712B /* callkit */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C69EFA0B209A0F660027712B /* JMCallKitEventListener.swift */, 0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
C69EFA09209A0F650027712B /* JMCallKitNotifier.swift */, C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */,
C69EFA0B209A0F660027712B /* JMCallKitListener.swift */,
C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */, C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */,
); );
path = callkit; path = callkit;
@ -407,10 +407,10 @@
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */, 0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */, 0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */, 0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
C69EFA0C209A0F660027712B /* JMCallKitNotifier.swift in Sources */, C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */, C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */, C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
C69EFA0E209A0F660027712B /* JMCallKitEventListener.swift in Sources */, C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */, 0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View File

@ -39,7 +39,7 @@ static NSString * const RNCallKitPerformSetMutedCallAction
static NSString * const RNCallKitProviderDidReset static NSString * const RNCallKitProviderDidReset
= @"providerDidReset"; = @"providerDidReset";
@interface RNCallKit : RCTEventEmitter <JMCallKitEventListener> @interface RNCallKit : RCTEventEmitter <JMCallKitListener>
@end @end
@implementation RNCallKit @implementation RNCallKit
@ -112,12 +112,12 @@ RCT_EXPORT_METHOD(setProviderConfiguration:(NSDictionary *)dictionary) {
dictionary); dictionary);
#endif #endif
if (![JMCallKitProxy hasProviderBeenConfigurated]) { if (![JMCallKitProxy isProviderConfigured]) {
[self configureProviderFromDictionary:dictionary]; [self configureProviderFromDictionary:dictionary];
} }
// register to receive CallKit proxy events // register to receive CallKit proxy events
[JMCallKitProxy addListener: self]; [JMCallKitProxy addListener:self];
} }
// Start outgoing call // Start outgoing call
@ -130,18 +130,16 @@ RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
NSLog(@"[RNCallKit][startCall] callUUID = %@", callUUID); NSLog(@"[RNCallKit][startCall] callUUID = %@", callUUID);
#endif #endif
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID]; // Don't start a new call if there's an active call for the specified
// callUUID. JitsiMeetView was configured for an incoming call.
// Don't start a call action if there's
// an active call for this UUID
// (i.e. JitsiMeetView was configured from an incoming call
if ([JMCallKitProxy hasActiveCallForUUID:callUUID]) { if ([JMCallKitProxy hasActiveCallForUUID:callUUID]) {
resolve(nil); resolve(nil);
return; return;
} }
CXHandle *handle_ CXHandle *handle_
= [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle]; = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
CXStartCallAction *action CXStartCallAction *action
= [[CXStartCallAction alloc] initWithCallUUID:callUUID_ = [[CXStartCallAction alloc] initWithCallUUID:callUUID_
handle:handle_]; handle:handle_];
@ -156,8 +154,8 @@ RCT_EXPORT_METHOD(reportCallFailed:(NSString *)callUUID
reject:(RCTPromiseRejectBlock)reject) { reject:(RCTPromiseRejectBlock)reject) {
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID]; NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
[JMCallKitProxy reportCallWith:callUUID_ [JMCallKitProxy reportCallWith:callUUID_
endedAt:nil endedAt:nil
reason:CXCallEndedReasonFailed]; reason:CXCallEndedReasonFailed];
resolve(nil); resolve(nil);
} }
@ -167,7 +165,7 @@ RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)callUUID
reject:(RCTPromiseRejectBlock)reject) { reject:(RCTPromiseRejectBlock)reject) {
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID]; NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
[JMCallKitProxy reportOutgoingCallWith:callUUID_ [JMCallKitProxy reportOutgoingCallWith:callUUID_
connectedAt:nil]; connectedAt:nil];
resolve(nil); resolve(nil);
} }
@ -186,12 +184,12 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID]; NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
NSString *displayName = options[@"displayName"]; NSString *displayName = options[@"displayName"];
BOOL hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue]; BOOL hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
[JMCallKitProxy reportCallUpdateWith:callUUID_ [JMCallKitProxy reportCallUpdateWith:callUUID_
handle:nil handle:nil
displayName:displayName displayName:displayName
hasVideo:hasVideo]; hasVideo:hasVideo];
resolve(nil); resolve(nil);
} }
@ -228,10 +226,11 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
} }
NSString *ringtoneSound = dictionary[@"ringtoneSound"]; NSString *ringtoneSound = dictionary[@"ringtoneSound"];
[JMCallKitProxy configureCallKitProviderWithLocalizedName:localizedName [JMCallKitProxy
ringtoneSound:ringtoneSound configureProviderWithLocalizedName:localizedName
iconTemplateImageData:iconTemplateImageData]; ringtoneSound:ringtoneSound
iconTemplateImageData:iconTemplateImageData];
} }
- (void)requestTransaction:(CXTransaction *)transaction - (void)requestTransaction:(CXTransaction *)transaction
@ -242,7 +241,7 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
#endif #endif
[JMCallKitProxy request:transaction [JMCallKitProxy request:transaction
completion:^(NSError * _Nullable error) { completion:^(NSError * _Nullable error) {
if (error) { if (error) {
NSLog( NSLog(
@"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)", @"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)",
@ -255,7 +254,7 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
}]; }];
} }
#pragma mark - JitsiMeetCallKitListener #pragma mark - JMCallKitListener
// Called when the provider has been reset. We should terminate all calls. // Called when the provider has been reset. We should terminate all calls.
- (void)providerDidReset { - (void)providerDidReset {
@ -307,7 +306,7 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction:]"); NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction:]");
#endif #endif
[JMCallKitProxy reportOutgoingCallWith:UUID [JMCallKitProxy reportOutgoingCallWith:UUID
startedConnectingAt:nil]; startedConnectingAt:nil];
} }
// The following just help with debugging: // The following just help with debugging:

View File

@ -18,87 +18,113 @@ import AVKit
import CallKit import CallKit
import Foundation import Foundation
internal final class JMCallKitNotifier: NSObject, CXProviderDelegate { internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
private var listeners = Set<JMCallKitEventListenerWrapper>() private var listeners = Set<JMCallKitEventListenerWrapper>()
internal override init() {} internal override init() {}
// MARK: - Add/remove listeners // MARK: - Add/remove listeners
func addListener(_ listener: JMCallKitEventListener) { func addListener(_ listener: JMCallKitListener) {
let wrapper = JMCallKitEventListenerWrapper(listener: listener) let wrapper = JMCallKitEventListenerWrapper(listener: listener)
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.insert(wrapper) listeners.insert(wrapper)
objc_sync_exit(listeners) objc_sync_exit(listeners)
} }
func removeListener(_ listener: JMCallKitEventListener) { func removeListener(_ listener: JMCallKitListener) {
let wrapper = JMCallKitEventListenerWrapper(listener: listener) let wrapper = JMCallKitEventListenerWrapper(listener: listener)
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.remove(wrapper) listeners.remove(wrapper)
objc_sync_exit(listeners) objc_sync_exit(listeners)
} }
// MARK: - CXProviderDelegate // MARK: - CXProviderDelegate
func providerDidReset(_ provider: CXProvider) { func providerDidReset(_ provider: CXProvider) {
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.forEach { $0.listener?.providerDidReset?() } listeners.forEach { $0.listener?.providerDidReset?() }
objc_sync_exit(listeners) objc_sync_exit(listeners)
} }
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.forEach { $0.listener?.performAnswerCall?(UUID: action.callUUID) } listeners.forEach {
$0.listener?.performAnswerCall?(UUID: action.callUUID)
}
objc_sync_exit(listeners) objc_sync_exit(listeners)
action.fulfill() action.fulfill()
} }
func provider(_ provider: CXProvider, perform action: CXEndCallAction) { func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.forEach { $0.listener?.performEndCall?(UUID: action.callUUID) } listeners.forEach {
$0.listener?.performEndCall?(UUID: action.callUUID)
}
objc_sync_exit(listeners) objc_sync_exit(listeners)
action.fulfill() action.fulfill()
} }
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.forEach { listeners.forEach {
$0.listener?.performSetMutedCall?(UUID: action.callUUID, $0.listener?.performSetMutedCall?(UUID: action.callUUID,
isMuted: action.isMuted) isMuted: action.isMuted)
} }
objc_sync_exit(listeners) objc_sync_exit(listeners)
action.fulfill() action.fulfill()
} }
func provider(_ provider: CXProvider, perform action: CXStartCallAction) { func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.forEach { listeners.forEach {
$0.listener?.performStartCall?(UUID: action.callUUID, $0.listener?.performStartCall?(UUID: action.callUUID,
isVideo: action.isVideo) isVideo: action.isVideo)
} }
objc_sync_exit(listeners) objc_sync_exit(listeners)
action.fulfill() action.fulfill()
} }
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { func provider(_ provider: CXProvider,
didActivate audioSession: AVAudioSession) {
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.forEach { listeners.forEach {
$0.listener?.providerDidActivateAudioSession?(session: audioSession) $0.listener?.providerDidActivateAudioSession?(session: audioSession)
} }
objc_sync_exit(listeners) objc_sync_exit(listeners)
} }
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { func provider(_ provider: CXProvider,
didDeactivate audioSession: AVAudioSession) {
objc_sync_enter(listeners) objc_sync_enter(listeners)
listeners.forEach { listeners.forEach {
$0.listener?.providerDidDeactivateAudioSession?(session: audioSession) $0.listener?.providerDidDeactivateAudioSession?(
session: audioSession)
} }
objc_sync_exit(listeners) objc_sync_exit(listeners)
} }
} }
fileprivate struct JMCallKitEventListenerWrapper: Hashable {
public var hashValue: Int
internal weak var listener: JMCallKitListener?
public init(listener: JMCallKitListener) {
self.listener = listener
self.hashValue = listener.hash
}
public static func ==(lhs: JMCallKitEventListenerWrapper,
rhs: JMCallKitEventListenerWrapper) -> Bool {
// XXX We're aware that "[t]wo instances with equal hash values are not
// necessarily equal to each other."
return lhs.hashValue == rhs.hashValue
}
}

View File

@ -18,46 +18,29 @@ import AVKit
import CallKit import CallKit
import Foundation import Foundation
@objc public protocol JMCallKitEventListener: NSObjectProtocol { @objc public protocol JMCallKitListener: NSObjectProtocol {
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc optional func providerDidReset() @objc optional func providerDidReset()
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc optional func performAnswerCall(UUID: UUID) @objc optional func performAnswerCall(UUID: UUID)
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc optional func performEndCall(UUID: UUID) @objc optional func performEndCall(UUID: UUID)
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc optional func performSetMutedCall(UUID: UUID, isMuted: Bool) @objc optional func performSetMutedCall(UUID: UUID, isMuted: Bool)
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc optional func performStartCall(UUID: UUID, isVideo: Bool) @objc optional func performStartCall(UUID: UUID, isVideo: Bool)
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc optional func providerDidActivateAudioSession(session: AVAudioSession) @objc optional func providerDidActivateAudioSession(session: AVAudioSession)
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc optional func providerDidDeactivateAudioSession(session: AVAudioSession) @objc optional func providerDidDeactivateAudioSession(session: AVAudioSession)
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc optional func providerTimedOutPerformingAction(action: CXAction) @objc optional func providerTimedOutPerformingAction(action: CXAction)
} }
internal struct JMCallKitEventListenerWrapper: Hashable {
public var hashValue: Int
internal weak var listener: JMCallKitEventListener?
public init(listener: JMCallKitEventListener) {
self.listener = listener
self.hashValue = listener.hash
}
public static func ==(lhs: JMCallKitEventListenerWrapper,
rhs: JMCallKitEventListenerWrapper) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}

View File

@ -20,138 +20,142 @@ import Foundation
/// JitsiMeet CallKit proxy /// JitsiMeet CallKit proxy
@available(iOS 10.0, *) @available(iOS 10.0, *)
@objc public final class JMCallKitProxy: NSObject { @objc public final class JMCallKitProxy: NSObject {
override private init() {} private override init() {}
// MARK: - CallKit proxy // MARK: - CallKit proxy
internal static let cxProvider: CXProvider = { private static let provider: CXProvider = {
let config = CXProviderConfiguration(localizedName: "") let configuration = CXProviderConfiguration(localizedName: "")
let provider = CXProvider(configuration: config) return CXProvider(configuration: configuration)
return provider
}() }()
internal static let cxCallController: CXCallController = { private static var providerConfiguration: CXProviderConfiguration? {
return CXCallController()
}()
internal static let callKitNotifier: JMCallKitNotifier = {
return JMCallKitNotifier()
}()
internal static var cxProviderConfiguration: CXProviderConfiguration? {
didSet { didSet {
guard let providerConfiguration = cxProviderConfiguration else { return } guard let configuration = providerConfiguration else { return }
cxProvider.configuration = providerConfiguration provider.configuration = configuration
cxProvider.setDelegate(callKitNotifier, queue: nil) provider.setDelegate(emitter, queue: nil)
} }
} }
/// Enables the proxy in between callkit and the consumers of the SDK private static let callController: CXCallController = {
/// Default to enabled, set to false when you don't want to use callkit return CXCallController()
}()
private static let emitter: JMCallKitEmitter = {
return JMCallKitEmitter()
}()
/// Enables the proxy in between CallKit and the consumers of the SDK.
/// Defaults to enabled, set to false when you don't want to use CallKit.
@objc public static var enabled: Bool = true { @objc public static var enabled: Bool = true {
didSet { didSet {
if enabled == false { if enabled == false {
cxProvider.setDelegate(nil, queue: nil) provider.setDelegate(nil, queue: nil)
} }
} }
} }
@objc public static func hasProviderBeenConfigurated() -> Bool { @objc public static func configureProvider(localizedName: String,
return cxProviderConfiguration != nil ringtoneSound: String?,
} iconTemplateImageData: Data?) {
@objc public static func configureCallKitProvider(localizedName: String,
ringtoneSound: String?,
iconTemplateImageData: Data?) {
let configuration = CXProviderConfiguration(localizedName: localizedName) let configuration = CXProviderConfiguration(localizedName: localizedName)
configuration.ringtoneSound = ringtoneSound
configuration.iconTemplateImageData = iconTemplateImageData configuration.iconTemplateImageData = iconTemplateImageData
configuration.maximumCallGroups = 1 configuration.maximumCallGroups = 1
configuration.maximumCallsPerCallGroup = 1 configuration.maximumCallsPerCallGroup = 1
configuration.ringtoneSound = ringtoneSound
configuration.supportedHandleTypes = [CXHandle.HandleType.generic] configuration.supportedHandleTypes = [CXHandle.HandleType.generic]
configuration.supportsVideo = true configuration.supportsVideo = true
cxProviderConfiguration = configuration
providerConfiguration = configuration
} }
@objc public static func addListener(_ listener: JMCallKitEventListener) { @objc public static func isProviderConfigured() -> Bool {
callKitNotifier.addListener(listener) return providerConfiguration != nil
} }
@objc public static func removeListener(_ listener: JMCallKitEventListener) { @objc public static func addListener(_ listener: JMCallKitListener) {
callKitNotifier.removeListener(listener) emitter.addListener(listener)
} }
@objc public static func removeListener(_ listener: JMCallKitListener) {
emitter.removeListener(listener)
}
@objc public static func hasActiveCallForUUID(_ callUUID: String) -> Bool { @objc public static func hasActiveCallForUUID(_ callUUID: String) -> Bool {
let activeCallForUUID = cxCallController.callObserver.calls.first { let activeCallForUUID = callController.callObserver.calls.first {
$0.uuid == UUID(uuidString: callUUID) $0.uuid == UUID(uuidString: callUUID)
} }
guard activeCallForUUID != nil else { return false } guard activeCallForUUID != nil else { return false }
return true return true
} }
@objc public static func reportNewIncomingCall(UUID: UUID, @objc public static func reportNewIncomingCall(
handle: String?, UUID: UUID,
displayName: String?, handle: String?,
hasVideo: Bool, displayName: String?,
completion: @escaping (Error?) -> Void) { hasVideo: Bool,
completion: @escaping (Error?) -> Void) {
guard enabled else { return } guard enabled else { return }
let callUpdate = makeCXUpdate(handle: handle, let callUpdate = makeCXUpdate(handle: handle,
displayName: displayName, displayName: displayName,
hasVideo: hasVideo) hasVideo: hasVideo)
cxProvider.reportNewIncomingCall(with: UUID, provider.reportNewIncomingCall(with: UUID,
update: callUpdate, update: callUpdate,
completion: completion) completion: completion)
} }
@objc public static func reportCallUpdate(with UUID: UUID, @objc public static func reportCallUpdate(with UUID: UUID,
handle: String?, handle: String?,
displayName: String?, displayName: String?,
hasVideo: Bool) { hasVideo: Bool) {
guard enabled else { return } guard enabled else { return }
let callUpdate = makeCXUpdate(handle: handle, let callUpdate = makeCXUpdate(handle: handle,
displayName: displayName, displayName: displayName,
hasVideo: hasVideo) hasVideo: hasVideo)
cxProvider.reportCall(with: UUID, updated: callUpdate) provider.reportCall(with: UUID, updated: callUpdate)
} }
@objc public static func reportCall(with UUID: UUID, @objc public static func reportCall(
endedAt dateEnded: Date?, with UUID: UUID,
reason endedReason: CXCallEndedReason) { endedAt dateEnded: Date?,
reason endedReason: CXCallEndedReason) {
guard enabled else { return } guard enabled else { return }
cxProvider.reportCall(with: UUID, provider.reportCall(with: UUID,
endedAt: dateEnded, endedAt: dateEnded,
reason: endedReason) reason: endedReason)
} }
@objc public static func reportOutgoingCall(with UUID: UUID, @objc public static func reportOutgoingCall(
startedConnectingAt dateStartedConnecting: Date?) { with UUID: UUID,
startedConnectingAt dateStartedConnecting: Date?) {
guard enabled else { return } guard enabled else { return }
cxProvider.reportOutgoingCall(with: UUID, provider.reportOutgoingCall(with: UUID,
startedConnectingAt: dateStartedConnecting) startedConnectingAt: dateStartedConnecting)
} }
@objc public static func reportOutgoingCall(with UUID: UUID, @objc public static func reportOutgoingCall(
connectedAt dateConnected: Date?) { with UUID: UUID,
connectedAt dateConnected: Date?) {
guard enabled else { return } guard enabled else { return }
cxProvider.reportOutgoingCall(with: UUID, connectedAt: dateConnected) provider.reportOutgoingCall(with: UUID, connectedAt: dateConnected)
} }
@objc public static func request(_ transaction: CXTransaction, @objc public static func request(
completion: @escaping (Error?) -> Swift.Void) { _ transaction: CXTransaction,
completion: @escaping (Error?) -> Swift.Void) {
guard enabled else { return } guard enabled else { return }
cxCallController.request(transaction, completion: completion) callController.request(transaction, completion: completion)
} }
// MARK: - Callkit Proxy helpers // MARK: - Callkit Proxy helpers
private static func makeCXUpdate(handle: String?, private static func makeCXUpdate(handle: String?,
displayName: String?, displayName: String?,
hasVideo: Bool) -> CXCallUpdate { hasVideo: Bool) -> CXCallUpdate {
@ -161,17 +165,16 @@ import Foundation
update.supportsGrouping = false update.supportsGrouping = false
update.supportsUngrouping = false update.supportsUngrouping = false
update.hasVideo = hasVideo update.hasVideo = hasVideo
if let handle = handle { if let handle = handle {
update.remoteHandle = CXHandle(type: .generic, update.remoteHandle = CXHandle(type: .generic,
value: handle) value: handle)
} }
if let displayName = displayName { if let displayName = displayName {
update.localizedCallerName = displayName update.localizedCallerName = displayName
} }
return update return update
} }
} }

View File

@ -15,48 +15,48 @@
*/ */
final class DragGestureController { final class DragGestureController {
var insets: UIEdgeInsets = UIEdgeInsets.zero var insets: UIEdgeInsets = UIEdgeInsets.zero
private var frameBeforeDragging: CGRect = CGRect.zero private var frameBeforeDragging: CGRect = CGRect.zero
private weak var view: UIView? private weak var view: UIView?
private lazy var panGesture: UIPanGestureRecognizer = { private lazy var panGesture: UIPanGestureRecognizer = {
return UIPanGestureRecognizer(target: self, return UIPanGestureRecognizer(target: self,
action: #selector(handlePan(gesture:))) action: #selector(handlePan(gesture:)))
}() }()
func startDragListener(inView view: UIView) { func startDragListener(inView view: UIView) {
self.view = view self.view = view
view.addGestureRecognizer(panGesture) view.addGestureRecognizer(panGesture)
panGesture.isEnabled = true panGesture.isEnabled = true
} }
func stopDragListener() { func stopDragListener() {
panGesture.isEnabled = false panGesture.isEnabled = false
view?.removeGestureRecognizer(panGesture) view?.removeGestureRecognizer(panGesture)
view = nil view = nil
} }
@objc private func handlePan(gesture: UIPanGestureRecognizer) { @objc private func handlePan(gesture: UIPanGestureRecognizer) {
guard let view = self.view else { return } guard let view = self.view else { return }
let translation = gesture.translation(in: view.superview) let translation = gesture.translation(in: view.superview)
let velocity = gesture.velocity(in: view.superview) let velocity = gesture.velocity(in: view.superview)
var frame = frameBeforeDragging var frame = frameBeforeDragging
switch gesture.state { switch gesture.state {
case .began: case .began:
frameBeforeDragging = view.frame frameBeforeDragging = view.frame
case .changed: case .changed:
frame.origin.x = floor(frame.origin.x + translation.x) frame.origin.x = floor(frame.origin.x + translation.x)
frame.origin.y = floor(frame.origin.y + translation.y) frame.origin.y = floor(frame.origin.y + translation.y)
view.frame = frame view.frame = frame
case .ended: case .ended:
let currentPos = view.frame.origin let currentPos = view.frame.origin
let finalPos = calculateFinalPosition() let finalPos = calculateFinalPosition()
let distance = CGPoint(x: currentPos.x - finalPos.x, let distance = CGPoint(x: currentPos.x - finalPos.x,
y: currentPos.y - finalPos.y) y: currentPos.y - finalPos.y)
let distanceMagnitude = magnitude(vector: distance) let distanceMagnitude = magnitude(vector: distance)
@ -64,9 +64,9 @@ final class DragGestureController {
let animationDuration = 0.5 let animationDuration = 0.5
let initialSpringVelocity = let initialSpringVelocity =
velocityMagnitude / distanceMagnitude / CGFloat(animationDuration) velocityMagnitude / distanceMagnitude / CGFloat(animationDuration)
frame.origin = CGPoint(x: finalPos.x, y: finalPos.y) frame.origin = CGPoint(x: finalPos.x, y: finalPos.y)
UIView.animate(withDuration: animationDuration, UIView.animate(withDuration: animationDuration,
delay: 0, delay: 0,
usingSpringWithDamping: 0.9, usingSpringWithDamping: 0.9,
@ -75,38 +75,38 @@ final class DragGestureController {
animations: { animations: {
view.frame = frame view.frame = frame
}, completion: nil) }, completion: nil)
default: default:
break break
} }
} }
private func calculateFinalPosition() -> CGPoint { private func calculateFinalPosition() -> CGPoint {
guard guard
let view = self.view, let view = self.view,
let bounds = view.superview?.frame let bounds = view.superview?.frame
else { return CGPoint.zero } else { return CGPoint.zero }
let currentSize = view.frame.size let currentSize = view.frame.size
let adjustedBounds = UIEdgeInsetsInsetRect(bounds, insets) let adjustedBounds = UIEdgeInsetsInsetRect(bounds, insets)
let threshold: CGFloat = 20.0 let threshold: CGFloat = 20.0
let velocity = panGesture.velocity(in: view.superview) let velocity = panGesture.velocity(in: view.superview)
let location = panGesture.location(in: view.superview) let location = panGesture.location(in: view.superview)
let goLeft: Bool let goLeft: Bool
if fabs(velocity.x) > threshold { if fabs(velocity.x) > threshold {
goLeft = velocity.x < -threshold goLeft = velocity.x < -threshold
} else { } else {
goLeft = location.x < bounds.midX goLeft = location.x < bounds.midX
} }
let goUp: Bool let goUp: Bool
if fabs(velocity.y) > threshold { if fabs(velocity.y) > threshold {
goUp = velocity.y < -threshold goUp = velocity.y < -threshold
} else { } else {
goUp = location.y < bounds.midY goUp = location.y < bounds.midY
} }
let finalPosX: CGFloat = let finalPosX: CGFloat =
goLeft goLeft
? adjustedBounds.origin.x ? adjustedBounds.origin.x
@ -115,10 +115,10 @@ final class DragGestureController {
goUp goUp
? adjustedBounds.origin.y ? adjustedBounds.origin.y
: bounds.size.height - insets.bottom - currentSize.height : bounds.size.height - insets.bottom - currentSize.height
return CGPoint(x: finalPosX, y: finalPosY) return CGPoint(x: finalPosX, y: finalPosY)
} }
private func magnitude(vector: CGPoint) -> CGFloat { private func magnitude(vector: CGPoint) -> CGFloat {
return sqrt(pow(vector.x, 2) + pow(vector.y, 2)) return sqrt(pow(vector.x, 2) + pow(vector.y, 2))
} }

View File

@ -21,7 +21,7 @@ public typealias AnimationCompletion = (Bool) -> Void
/// This object will also provide the drag and tap interactions of the view /// This object will also provide the drag and tap interactions of the view
/// when is presented in Picure in Picture mode. /// when is presented in Picure in Picture mode.
public class PiPViewCoordinator { public class PiPViewCoordinator {
/// Limits the boundries of view position on screen when minimized /// Limits the boundries of view position on screen when minimized
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25, public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
left: 5, left: 5,
@ -31,7 +31,7 @@ public class PiPViewCoordinator {
dragController.insets = dragBoundInsets dragController.insets = dragBoundInsets
} }
} }
/// The size ratio of the view when in PiP mode /// The size ratio of the view when in PiP mode
public var pipSizeRatio: CGFloat = { public var pipSizeRatio: CGFloat = {
let deviceIdiom = UIScreen.main.traitCollection.userInterfaceIdiom let deviceIdiom = UIScreen.main.traitCollection.userInterfaceIdiom
@ -44,21 +44,21 @@ public class PiPViewCoordinator {
return 0.25 return 0.25
} }
}() }()
private(set) var isInPiP: Bool = false // true if view is in PiP mode private(set) var isInPiP: Bool = false // true if view is in PiP mode
private(set) var view: UIView private(set) var view: UIView
private var currentBounds: CGRect = CGRect.zero private var currentBounds: CGRect = CGRect.zero
private var tapGestureRecognizer: UITapGestureRecognizer? private var tapGestureRecognizer: UITapGestureRecognizer?
private var exitPiPButton: UIButton? private var exitPiPButton: UIButton?
private let dragController: DragGestureController = DragGestureController() private let dragController: DragGestureController = DragGestureController()
public init(withView view: UIView) { public init(withView view: UIView) {
self.view = view self.view = view
} }
/// Configure the view to be always on top of all the contents /// Configure the view to be always on top of all the contents
/// of the provided parent view. /// of the provided parent view.
/// If a parentView is not provided it will try to use the main window /// If a parentView is not provided it will try to use the main window
@ -66,25 +66,25 @@ public class PiPViewCoordinator {
guard guard
let parentView = parentView ?? UIApplication.shared.keyWindow let parentView = parentView ?? UIApplication.shared.keyWindow
else { return } else { return }
parentView.addSubview(view) parentView.addSubview(view)
currentBounds = parentView.bounds currentBounds = parentView.bounds
view.frame = currentBounds view.frame = currentBounds
view.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude) view.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
} }
/// Show view with fade in animation /// Show view with fade in animation
public func show(completion: AnimationCompletion? = nil) { public func show(completion: AnimationCompletion? = nil) {
if view.isHidden || view.alpha < 1 { if view.isHidden || view.alpha < 1 {
view.isHidden = false view.isHidden = false
view.alpha = 0 view.alpha = 0
animateTransition(animations: { [weak self] in animateTransition(animations: { [weak self] in
self?.view.alpha = 1 self?.view.alpha = 1
}, completion: completion) }, completion: completion)
} }
} }
/// Hide view with fade out animation /// Hide view with fade out animation
public func hide(completion: AnimationCompletion? = nil) { public func hide(completion: AnimationCompletion? = nil) {
if view.isHidden || view.alpha > 0 { if view.isHidden || view.alpha > 0 {
@ -94,7 +94,7 @@ public class PiPViewCoordinator {
}, completion: completion) }, completion: completion)
} }
} }
/// Resize view to and change state to custom PictureInPicture mode /// Resize view to and change state to custom PictureInPicture mode
/// This will resize view, add a gesture to enable user to "drag" view /// This will resize view, add a gesture to enable user to "drag" view
/// around screen, and add a button of top of the view to be able to exit mode /// around screen, and add a button of top of the view to be able to exit mode
@ -103,7 +103,7 @@ public class PiPViewCoordinator {
animateViewChange() animateViewChange()
dragController.startDragListener(inView: view) dragController.startDragListener(inView: view)
dragController.insets = dragBoundInsets dragController.insets = dragBoundInsets
// add single tap gesture recognition for displaying exit PiP UI // add single tap gesture recognition for displaying exit PiP UI
let exitSelector = #selector(toggleExitPiP) let exitSelector = #selector(toggleExitPiP)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, let tapGestureRecognizer = UITapGestureRecognizer(target: self,
@ -111,36 +111,36 @@ public class PiPViewCoordinator {
self.tapGestureRecognizer = tapGestureRecognizer self.tapGestureRecognizer = tapGestureRecognizer
view.addGestureRecognizer(tapGestureRecognizer) view.addGestureRecognizer(tapGestureRecognizer)
} }
/// Exit Picture in picture mode, this will resize view, remove /// Exit Picture in picture mode, this will resize view, remove
/// exit pip button, and disable the drag gesture /// exit pip button, and disable the drag gesture
@objc public func exitPictureInPicture() { @objc public func exitPictureInPicture() {
isInPiP = false isInPiP = false
animateViewChange() animateViewChange()
dragController.stopDragListener() dragController.stopDragListener()
// hide PiP UI // hide PiP UI
exitPiPButton?.removeFromSuperview() exitPiPButton?.removeFromSuperview()
exitPiPButton = nil exitPiPButton = nil
// remove gesture // remove gesture
let exitSelector = #selector(toggleExitPiP) let exitSelector = #selector(toggleExitPiP)
tapGestureRecognizer?.removeTarget(self, action: exitSelector) tapGestureRecognizer?.removeTarget(self, action: exitSelector)
tapGestureRecognizer = nil tapGestureRecognizer = nil
} }
/// Reset view to provide bounds, use this method on rotation or /// Reset view to provide bounds, use this method on rotation or
/// screen size changes /// screen size changes
public func resetBounds(bounds: CGRect) { public func resetBounds(bounds: CGRect) {
currentBounds = bounds currentBounds = bounds
exitPictureInPicture() exitPictureInPicture()
} }
/// Stop the dragging gesture of the root view /// Stop the dragging gesture of the root view
public func stopDragGesture() { public func stopDragGesture() {
dragController.stopDragListener() dragController.stopDragListener()
} }
/// Customize the presentation of exit pip button /// Customize the presentation of exit pip button
open func configureExitPiPButton(target: Any, open func configureExitPiPButton(target: Any,
action: Selector) -> UIButton { action: Selector) -> UIButton {
@ -157,9 +157,9 @@ public class PiPViewCoordinator {
button.addTarget(target, action: action, for: .touchUpInside) button.addTarget(target, action: action, for: .touchUpInside)
return button return button
} }
// MARK: - Interactions // MARK: - Interactions
@objc private func toggleExitPiP() { @objc private func toggleExitPiP() {
if exitPiPButton == nil { if exitPiPButton == nil {
// show button // show button
@ -168,30 +168,30 @@ public class PiPViewCoordinator {
action: exitSelector) action: exitSelector)
view.addSubview(button) view.addSubview(button)
exitPiPButton = button exitPiPButton = button
} else { } else {
// hide button // hide button
exitPiPButton?.removeFromSuperview() exitPiPButton?.removeFromSuperview()
exitPiPButton = nil exitPiPButton = nil
} }
} }
// MARK: - Size calculation // MARK: - Size calculation
private func animateViewChange() { private func animateViewChange() {
UIView.animate(withDuration: 0.25) { UIView.animate(withDuration: 0.25) {
self.view.frame = self.changeViewRect() self.view.frame = self.changeViewRect()
self.view.setNeedsLayout() self.view.setNeedsLayout()
} }
} }
private func changeViewRect() -> CGRect { private func changeViewRect() -> CGRect {
let bounds = currentBounds let bounds = currentBounds
guard isInPiP else { guard isInPiP else {
return bounds return bounds
} }
// resize to suggested ratio and position to the bottom right // resize to suggested ratio and position to the bottom right
let adjustedBounds = UIEdgeInsetsInsetRect(bounds, dragBoundInsets) let adjustedBounds = UIEdgeInsetsInsetRect(bounds, dragBoundInsets)
let size = CGSize(width: bounds.size.width * pipSizeRatio, let size = CGSize(width: bounds.size.width * pipSizeRatio,
@ -200,9 +200,9 @@ public class PiPViewCoordinator {
let y: CGFloat = adjustedBounds.maxY - size.height let y: CGFloat = adjustedBounds.maxY - size.height
return CGRect(x: x, y: y, width: size.width, height: size.height) return CGRect(x: x, y: y, width: size.width, height: size.height)
} }
// MARK: - Animation helpers // MARK: - Animation helpers
private func animateTransition(animations: @escaping () -> Void, private func animateTransition(animations: @escaping () -> Void,
completion: AnimationCompletion?) { completion: AnimationCompletion?) {
UIView.animate(withDuration: 0.1, UIView.animate(withDuration: 0.1,
@ -211,5 +211,5 @@ public class PiPViewCoordinator {
animations: animations, animations: animations,
completion: completion) completion: completion)
} }
} }

View File

@ -2,10 +2,7 @@
import uuid from 'uuid'; import uuid from 'uuid';
import { import { createTrackMutedEvent, sendAnalytics } from '../../analytics';
createTrackMutedEvent,
sendAnalytics
} from '../../analytics';
import { import {
APP_WILL_MOUNT, APP_WILL_MOUNT,
APP_WILL_UNMOUNT, APP_WILL_UNMOUNT,
@ -29,16 +26,13 @@ import {
setAudioMuted setAudioMuted
} from '../../base/media'; } from '../../base/media';
import { MiddlewareRegistry } from '../../base/redux'; import { MiddlewareRegistry } from '../../base/redux';
import { import { TRACK_CREATE_ERROR, isLocalTrackMuted } from '../../base/tracks';
TRACK_CREATE_ERROR,
isLocalTrackMuted
} from '../../base/tracks';
import { _SET_CALLKIT_SUBSCRIPTIONS } from './actionTypes'; import { _SET_CALLKIT_SUBSCRIPTIONS } from './actionTypes';
import CallKit from './CallKit'; import CallKit from './CallKit';
/** /**
* Middleware that captures several system actions and hooks up CallKit. * Middleware that captures system actions and hooks up CallKit.
* *
* @param {Store} store - The redux store. * @param {Store} store - The redux store.
* @returns {Function} * @returns {Function}
@ -84,17 +78,17 @@ CallKit && MiddlewareRegistry.register(store => next => action => {
}); });
/** /**
* Notifies the feature jwt that the action {@link APP_WILL_MOUNT} is being * Notifies the feature callkit that the action {@link APP_WILL_MOUNT} is being
* dispatched within a specific redux {@code store}. * dispatched within a specific redux {@code store}.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is * @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
* being dispatched in the specified {@code store}. * being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _appWillMount({ dispatch, getState }, next, action) { function _appWillMount({ dispatch, getState }, next, action) {
const result = next(action); const result = next(action);
@ -119,8 +113,8 @@ function _appWillMount({ dispatch, getState }, next, action) {
context), context),
// According to CallKit's documentation, when the system resets we // According to CallKit's documentation, when the system resets we
// should terminate all calls. Hence, providerDidReset is the same // should terminate all calls. Hence, providerDidReset is the same to us
// to us as performEndCallAction. // as performEndCallAction.
CallKit.addListener( CallKit.addListener(
'providerDidReset', 'providerDidReset',
_onPerformEndCallAction, _onPerformEndCallAction,
@ -136,17 +130,17 @@ function _appWillMount({ dispatch, getState }, next, action) {
} }
/** /**
* Notifies the feature jwt that the action {@link CONFERENCE_FAILED} is being * Notifies the feature callkit that the action {@link CONFERENCE_FAILED} is
* dispatched within a specific redux {@code store}. * being dispatched within a specific redux {@code store}.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_FAILED} which is * @param {Action} action - The redux action {@code CONFERENCE_FAILED} which is
* being dispatched in the specified {@code store}. * being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _conferenceFailed(store, next, action) { function _conferenceFailed(store, next, action) {
const result = next(action); const result = next(action);
@ -166,17 +160,17 @@ function _conferenceFailed(store, next, action) {
} }
/** /**
* Notifies the feature jwt that the action {@link CONFERENCE_JOINED} is being * Notifies the feature callkit that the action {@link CONFERENCE_JOINED} is
* dispatched within a specific redux {@code store}. * being dispatched within a specific redux {@code store}.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_JOINED} which is * @param {Action} action - The redux action {@code CONFERENCE_JOINED} which is
* being dispatched in the specified {@code store}. * being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _conferenceJoined(store, next, action) { function _conferenceJoined(store, next, action) {
const result = next(action); const result = next(action);
@ -191,17 +185,17 @@ function _conferenceJoined(store, next, action) {
} }
/** /**
* Notifies the feature jwt that the action {@link CONFERENCE_LEFT} is being * Notifies the feature callkit that the action {@link CONFERENCE_LEFT} is being
* dispatched within a specific redux {@code store}. * dispatched within a specific redux {@code store}.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_LEFT} which is * @param {Action} action - The redux action {@code CONFERENCE_LEFT} which is
* being dispatched in the specified {@code store}. * being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _conferenceLeft(store, next, action) { function _conferenceLeft(store, next, action) {
const result = next(action); const result = next(action);
@ -216,29 +210,29 @@ function _conferenceLeft(store, next, action) {
} }
/** /**
* Notifies the feature jwt that the action {@link CONFERENCE_WILL_JOIN} is * Notifies the feature callkit that the action {@link CONFERENCE_WILL_JOIN} is
* being dispatched within a specific redux {@code store}. * being dispatched within a specific redux {@code store}.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_WILL_JOIN} which * @param {Action} action - The redux action {@code CONFERENCE_WILL_JOIN} which
* is being dispatched in the specified {@code store}. * is being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _conferenceWillJoin({ getState }, next, action) { function _conferenceWillJoin({ getState }, next, action) {
const result = next(action); const result = next(action);
const { conference } = action; const { conference } = action;
const state = getState(); const state = getState();
const { callUUID } = state['features/base/config'];
const url = getInviteURL(state); const url = getInviteURL(state);
const hasVideo = !isVideoMutedByAudioOnly(state); const hasVideo = !isVideoMutedByAudioOnly(state);
const { callUUID } = state['features/base/config'];
// When assigning the call UUID, do so in upper case, since iOS will // When assigning the call UUID, do so in upper case, since iOS will return
// return it upper cased. // it upper cased.
conference.callUUID = (callUUID || uuid.v4()).toUpperCase(); conference.callUUID = (callUUID || uuid.v4()).toUpperCase();
CallKit.startCall(conference.callUUID, url.toString(), hasVideo) CallKit.startCall(conference.callUUID, url.toString(), hasVideo)
@ -306,17 +300,17 @@ function _onPerformSetMutedCallAction({ callUUID, muted: newValue }) {
} }
/** /**
* Notifies the feature jwt that the action {@link SET_AUDIO_MUTED} is being * Notifies the feature callkit that the action {@link SET_AUDIO_MUTED} is being
* dispatched within a specific redux {@code store}. * dispatched within a specific redux {@code store}.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code SET_AUDIO_MUTED} which is * @param {Action} action - The redux action {@code SET_AUDIO_MUTED} which is
* being dispatched in the specified {@code store}. * being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _setAudioMuted({ getState }, next, action) { function _setAudioMuted({ getState }, next, action) {
const result = next(action); const result = next(action);
@ -331,17 +325,18 @@ function _setAudioMuted({ getState }, next, action) {
} }
/** /**
* Notifies the feature jwt that the action {@link _SET_CALLKIT_SUBSCRIPTIONS} * Notifies the feature callkit that the action
* is being dispatched within a specific redux {@code store}. * {@link _SET_CALLKIT_SUBSCRIPTIONS} is being dispatched within a specific
* redux {@code store}.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code _SET_CALLKIT_SUBSCRIPTIONS} * @param {Action} action - The redux action {@code _SET_CALLKIT_SUBSCRIPTIONS}
* which is being dispatched in the specified {@code store}. * which is being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _setCallKitSubscriptions({ getState }, next, action) { function _setCallKitSubscriptions({ getState }, next, action) {
const { subscriptions } = getState()['features/callkit']; const { subscriptions } = getState()['features/callkit'];
@ -356,17 +351,17 @@ function _setCallKitSubscriptions({ getState }, next, action) {
} }
/** /**
* Notifies the feature jwt that the action {@link SET_VIDEO_MUTED} is being * Notifies the feature callkit that the action {@link SET_VIDEO_MUTED} is being
* dispatched within a specific redux {@code store}. * dispatched within a specific redux {@code store}.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code SET_VIDEO_MUTED} which is * @param {Action} action - The redux action {@code SET_VIDEO_MUTED} which is
* being dispatched in the specified {@code store}. * being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _setVideoMuted({ getState }, next, action) { function _setVideoMuted({ getState }, next, action) {
const result = next(action); const result = next(action);
@ -385,17 +380,17 @@ function _setVideoMuted({ getState }, next, action) {
/** /**
* Handles a track creation failure. This is relevant to us in the following * Handles a track creation failure. This is relevant to us in the following
* (corner) case: if the user never gave their permission to use the microphone * (corner) case: if the user never gave their permission to use the microphone
* and try to unmute from the CallKit interface, this will fail, and we need * and try to unmute from the CallKit interface, this will fail, and we need to
* to sync back the CallKit button state. * sync back the CallKit button state.
* *
* @param {Store} store - The redux store in which the specified {@code action} * @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched. * is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}. * specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code TRACK_CREARE_ERROR} which is * @param {Action} action - The redux action {@code TRACK_CREARE_ERROR} which is
* being dispatched in the specified {@code store}. * being dispatched in the specified {@code store}.
* @private * @private
* @returns {*} * @returns {*} The value returned by {@code next(action)}.
*/ */
function _trackCreateError({ getState }, next, action) { function _trackCreateError({ getState }, next, action) {
const result = next(action); const result = next(action);