From 5858859838c93ae40543ff0edf821751eacd3fac Mon Sep 17 00:00:00 2001 From: Daniel Ornelas Date: Tue, 20 Mar 2018 11:47:41 -0500 Subject: [PATCH] Addressing feedback from PR --- ios/app/src/Info.plist | 6 +- .../project.pbxproj | 26 ++--- ios/example-pip-app/src/AppDelegate.swift | 24 +++-- .../src/Base.lproj/Main.storyboard | 8 +- ios/example-pip-app/src/ViewController.swift | 37 ++++--- .../contents.xcworkspacedata | 2 +- ios/sdk/sdk.xcodeproj/project.pbxproj | 28 ++--- .../JitsiMeetManager/JitsiMeetManager.swift | 101 ------------------ .../DragGestureController.swift | 33 ++++-- .../JitsiMeetPresentationCoordinator.swift | 97 +++++++++++++++++ .../JitsiMeetViewController.swift | 41 +++++-- .../PiPWindow.swift | 84 +++++++++------ .../picture-in-picture}/image-resize@2x.png | Bin .../picture-in-picture}/image-resize@3x.png | Bin 14 files changed, 277 insertions(+), 210 deletions(-) rename ios/example-pip-app/{PiPApp.xcodeproj => example-pip-app.xcodeproj}/project.pbxproj (95%) delete mode 100644 ios/sdk/src/JitsiMeetManager/JitsiMeetManager.swift rename ios/sdk/src/{JitsiMeetManager/PiPWindow => picture-in-picture}/DragGestureController.swift (73%) create mode 100644 ios/sdk/src/picture-in-picture/JitsiMeetPresentationCoordinator.swift rename ios/sdk/src/{JitsiMeetManager => picture-in-picture}/JitsiMeetViewController.swift (63%) rename ios/sdk/src/{JitsiMeetManager/PiPWindow => picture-in-picture}/PiPWindow.swift (65%) rename ios/sdk/{ => src/picture-in-picture}/image-resize@2x.png (100%) rename ios/sdk/{ => src/picture-in-picture}/image-resize@3x.png (100%) diff --git a/ios/app/src/Info.plist b/ios/app/src/Info.plist index ae7f21e1c..39d6437c9 100644 --- a/ios/app/src/Info.plist +++ b/ios/app/src/Info.plist @@ -55,14 +55,14 @@ - NSCalendarsUsageDescription - Displays the user's meetings in the app. + NSCalendarsUsageDescription + See your scheduled conferences in the app. NSCameraUsageDescription Participate in conferences with video. NSLocationWhenInUseUsageDescription NSMicrophoneUsageDescription - Participate in conferences with audio. + Participate in conferences with voice. UIBackgroundModes audio diff --git a/ios/example-pip-app/PiPApp.xcodeproj/project.pbxproj b/ios/example-pip-app/example-pip-app.xcodeproj/project.pbxproj similarity index 95% rename from ios/example-pip-app/PiPApp.xcodeproj/project.pbxproj rename to ios/example-pip-app/example-pip-app.xcodeproj/project.pbxproj index 162437c52..dfca5f413 100644 --- a/ios/example-pip-app/PiPApp.xcodeproj/project.pbxproj +++ b/ios/example-pip-app/example-pip-app.xcodeproj/project.pbxproj @@ -35,7 +35,7 @@ /* Begin PBXFileReference section */ C6A34247204DF18000E062DD /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = ""; }; - C6F99C37204DE6BE0001F710 /* PiPApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PiPApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C6F99C37204DE6BE0001F710 /* example-pip-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example-pip-app.app; sourceTree = BUILT_PRODUCTS_DIR; }; C6F99C3A204DE6BE0001F710 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C6F99C3C204DE6BE0001F710 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; C6F99C3F204DE6BE0001F710 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -70,7 +70,7 @@ C6F99C38204DE6BE0001F710 /* Products */ = { isa = PBXGroup; children = ( - C6F99C37204DE6BE0001F710 /* PiPApp.app */, + C6F99C37204DE6BE0001F710 /* example-pip-app.app */, ); name = Products; sourceTree = ""; @@ -100,9 +100,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - C6F99C36204DE6BE0001F710 /* PiPApp */ = { + C6F99C36204DE6BE0001F710 /* example-pip-app */ = { isa = PBXNativeTarget; - buildConfigurationList = C6F99C49204DE6BE0001F710 /* Build configuration list for PBXNativeTarget "PiPApp" */; + buildConfigurationList = C6F99C49204DE6BE0001F710 /* Build configuration list for PBXNativeTarget "example-pip-app" */; buildPhases = ( C6A3424A204DF91D00E062DD /* Run Adjust ATS for loading JS bundle */, C6F99C62204DEFFE0001F710 /* Run React Packager */, @@ -116,9 +116,9 @@ ); dependencies = ( ); - name = PiPApp; - productName = ExampleAppUsingJitsiWithPiP; - productReference = C6F99C37204DE6BE0001F710 /* PiPApp.app */; + name = example-pip-app; + productName = example-pip-app; + productReference = C6F99C37204DE6BE0001F710 /* example-pip-app.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -142,7 +142,7 @@ }; }; }; - buildConfigurationList = C6F99C32204DE6BE0001F710 /* Build configuration list for PBXProject "PiPApp" */; + buildConfigurationList = C6F99C32204DE6BE0001F710 /* Build configuration list for PBXProject "example-pip-app" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -155,7 +155,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - C6F99C36204DE6BE0001F710 /* PiPApp */, + C6F99C36204DE6BE0001F710 /* example-pip-app */, ); }; /* End PBXProject section */ @@ -366,7 +366,7 @@ INFOPLIST_FILE = src/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.jitsi.PiPApp; + PRODUCT_BUNDLE_IDENTIFIER = com.jitsi.example-pip-app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -382,7 +382,7 @@ INFOPLIST_FILE = src/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.jitsi.PiPApp; + PRODUCT_BUNDLE_IDENTIFIER = com.jitsi.example-pip-app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -392,7 +392,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - C6F99C32204DE6BE0001F710 /* Build configuration list for PBXProject "PiPApp" */ = { + C6F99C32204DE6BE0001F710 /* Build configuration list for PBXProject "example-pip-app" */ = { isa = XCConfigurationList; buildConfigurations = ( C6F99C47204DE6BE0001F710 /* Debug */, @@ -401,7 +401,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C6F99C49204DE6BE0001F710 /* Build configuration list for PBXNativeTarget "PiPApp" */ = { + C6F99C49204DE6BE0001F710 /* Build configuration list for PBXNativeTarget "example-pip-app" */ = { isa = XCConfigurationList; buildConfigurations = ( C6F99C4A204DE6BE0001F710 /* Debug */, diff --git a/ios/example-pip-app/src/AppDelegate.swift b/ios/example-pip-app/src/AppDelegate.swift index 1f935761c..96bb81ec6 100644 --- a/ios/example-pip-app/src/AppDelegate.swift +++ b/ios/example-pip-app/src/AppDelegate.swift @@ -1,10 +1,18 @@ -// -// AppDelegate.swift -// ExampleAppUsingJitsiWithPiP -// -// Created by Daniel Ornelas on 3/5/18. -// Copyright © 2018 Atlassian Inc. All rights reserved. -// +/* + * Copyright @ 2017-present Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import JitsiMeet @@ -30,6 +38,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { return JitsiMeetView.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation) } - } - diff --git a/ios/example-pip-app/src/Base.lproj/Main.storyboard b/ios/example-pip-app/src/Base.lproj/Main.storyboard index 35ee2b87b..fc16ccefb 100644 --- a/ios/example-pip-app/src/Base.lproj/Main.storyboard +++ b/ios/example-pip-app/src/Base.lproj/Main.storyboard @@ -13,17 +13,17 @@ - + diff --git a/ios/example-pip-app/src/ViewController.swift b/ios/example-pip-app/src/ViewController.swift index 64a4723ee..981285b5d 100644 --- a/ios/example-pip-app/src/ViewController.swift +++ b/ios/example-pip-app/src/ViewController.swift @@ -1,10 +1,18 @@ -// -// ViewController.swift -// ExampleAppUsingJitsiWithPiP -// -// Created by Daniel Ornelas on 3/5/18. -// Copyright © 2018 Atlassian Inc. All rights reserved. -// +/* + * Copyright @ 2017-present Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import UIKit import JitsiMeet @@ -13,7 +21,7 @@ class ViewController: UIViewController { @IBOutlet weak var videoButton: UIButton? - private var jitsiMeetManager: JitsiMeetManager? + private var jitsiMeetCoordinator: JitsiMeetPresentationCoordinator? override func viewDidLoad() { super.viewDidLoad() @@ -21,12 +29,11 @@ class ViewController: UIViewController { // MARK: - Actions - @IBAction func startMeeting(sender: Any?) { - //let url = URL(string: "") - self.jitsiMeetManager = JitsiMeetManager() - jitsiMeetManager?.welcomeScreenEnabled = true - jitsiMeetManager?.load(withUrl: nil) + @IBAction func openJitsiMeet(sender: Any?) { + let jitsiMeetCoordinator = JitsiMeetPresentationCoordinator() + self.jitsiMeetCoordinator = jitsiMeetCoordinator + jitsiMeetCoordinator.jitsiMeetView().welcomePageEnabled = true + jitsiMeetCoordinator.jitsiMeetView().load(nil) + jitsiMeetCoordinator.show() } - } - diff --git a/ios/jitsi-meet.xcworkspace/contents.xcworkspacedata b/ios/jitsi-meet.xcworkspace/contents.xcworkspacedata index ed0af25d3..bdcb0765e 100644 --- a/ios/jitsi-meet.xcworkspace/contents.xcworkspacedata +++ b/ios/jitsi-meet.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:example-pip-app/example-pip-app.xcodeproj"> diff --git a/ios/sdk/sdk.xcodeproj/project.pbxproj b/ios/sdk/sdk.xcodeproj/project.pbxproj index b3759bcd9..5e7da94bd 100644 --- a/ios/sdk/sdk.xcodeproj/project.pbxproj +++ b/ios/sdk/sdk.xcodeproj/project.pbxproj @@ -30,7 +30,7 @@ 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 */; }; C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425C204EF76800E062DD /* PiPWindow.swift */; }; - C6A34260204EF76800E062DD /* JitsiMeetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425D204EF76800E062DD /* JitsiMeetManager.swift */; }; + C6A34260204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */; }; C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; }; C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */; }; C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; }; @@ -62,10 +62,10 @@ 0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = ""; }; 9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = ""; }; - C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-resize@2x.png"; sourceTree = ""; }; - C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "image-resize@3x.png"; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; C6A3425C204EF76800E062DD /* PiPWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PiPWindow.swift; sourceTree = ""; }; - C6A3425D204EF76800E062DD /* JitsiMeetManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JitsiMeetManager.swift; sourceTree = ""; }; + C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JitsiMeetPresentationCoordinator.swift; sourceTree = ""; }; C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = ""; }; C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetViewController.swift; sourceTree = ""; }; C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = ""; }; @@ -119,7 +119,7 @@ 0BD906E71EC0C00300C8C18E /* src */ = { isa = PBXGroup; children = ( - C6A3426B204F127900E062DD /* JitsiMeetManager */, + C6A3426B204F127900E062DD /* picture-in-picture */, 0BCA495C1EC4B6C600B793EE /* AudioMode.m */, 0BB9AD7C1F60356D001C08DB /* AppInfo.m */, 0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */, @@ -161,23 +161,15 @@ name = Pods; sourceTree = ""; }; - C6245F5A2052043F0040BE68 /* PiPWindow */ = { + C6A3426B204F127900E062DD /* picture-in-picture */ = { isa = PBXGroup; children = ( C6A3425E204EF76800E062DD /* DragGestureController.swift */, + C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */, + C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */, C6A3425C204EF76800E062DD /* PiPWindow.swift */, ); - path = PiPWindow; - sourceTree = ""; - }; - C6A3426B204F127900E062DD /* JitsiMeetManager */ = { - isa = PBXGroup; - children = ( - C6245F5A2052043F0040BE68 /* PiPWindow */, - C6A3425D204EF76800E062DD /* JitsiMeetManager.swift */, - C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */, - ); - path = JitsiMeetManager; + path = "picture-in-picture"; sourceTree = ""; }; /* End PBXGroup section */ @@ -345,7 +337,7 @@ buildActionMask = 2147483647; files = ( 0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */, - C6A34260204EF76800E062DD /* JitsiMeetManager.swift in Sources */, + C6A34260204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift in Sources */, 0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */, 0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */, 0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */, diff --git a/ios/sdk/src/JitsiMeetManager/JitsiMeetManager.swift b/ios/sdk/src/JitsiMeetManager/JitsiMeetManager.swift deleted file mode 100644 index abd24b918..000000000 --- a/ios/sdk/src/JitsiMeetManager/JitsiMeetManager.swift +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright © 2018 Jitsi. All rights reserved. - -import Foundation - -/// Creates and coordinates the presentation of JitsiMeetViewController inside of an external window -/// which can be resized and dragged with custom PiP mode -open class JitsiMeetManager: NSObject { - - /// Defines if welcome screen should be on - public var welcomeScreenEnabled: Bool = false { - didSet { - meetViewController?.jitsiMeetView.welcomePageEnabled = welcomeScreenEnabled - } - } - - private(set) var meetViewController: JitsiMeetViewController? - private(set) var meetWindow: PiPWindow? - - override public init() { - super.init() - self.meetViewController = makeMeetViewController() - self.meetWindow = makeMeetWindow() - } - - public init(meetViewController: JitsiMeetViewController? = nil, meetWindow: PiPWindow? = nil) { - super.init() - self.meetViewController = meetViewController ?? makeMeetViewController() - self.meetWindow = meetWindow ?? makeMeetWindow() - } - - /// Presents and loads a jitsi meet view - /// - /// - Parameter url: The url of the presentation - public func load(withUrl url: URL?) { - meetWindow?.show() - meetViewController?.jitsiMeetView.load(url) - } - - /// Presents and loads a jitsi meet view with configuration - /// - /// - Parameter urlObject: A dictionary of keys to be used for configuration - public func load(withUrlObject urlObject: [AnyHashable : Any]?) { - meetWindow?.show() - meetViewController?.jitsiMeetView.loadURLObject(urlObject) - } - - deinit { - cleanUp() - } - - // MARK: - helpers - - fileprivate func cleanUp() { - // TODO: more clean up work on this - - meetWindow?.isHidden = true - meetWindow?.stopDragGesture() - } - - private func makeMeetViewController() -> JitsiMeetViewController { - let vc = JitsiMeetViewController() - vc.jitsiMeetView.welcomePageEnabled = self.welcomeScreenEnabled - vc.jitsiMeetView.pictureInPictureEnabled = true - vc.delegate = self - return vc - } - - private func makeMeetWindow() -> PiPWindow { - let window = PiPWindow(frame: UIScreen.main.bounds) - window.backgroundColor = .clear - window.windowLevel = UIWindowLevelStatusBar + 100 - window.rootViewController = self.meetViewController - return window - } -} - -extension JitsiMeetManager: JitsiMeetViewControllerDelegate { - - open func performPresentationUpdate(to: JitsiMeetPresentationUpdate) { - guard let meetWindow = self.meetWindow else { return } - - switch to { - case .enterPiP: - meetWindow.goToPiP() - case .traitChange: - // resize to full screen if rotation happens - if meetWindow.isInPiP { - meetWindow.goToFullScreen() - } - } - } - - open func meetingStarted() { - // do something - } - - open func meetingEnded(wasFailure: Bool) { - cleanUp() - } -} - diff --git a/ios/sdk/src/JitsiMeetManager/PiPWindow/DragGestureController.swift b/ios/sdk/src/picture-in-picture/DragGestureController.swift similarity index 73% rename from ios/sdk/src/JitsiMeetManager/PiPWindow/DragGestureController.swift rename to ios/sdk/src/picture-in-picture/DragGestureController.swift index 92f3b1b34..bf0017b0f 100644 --- a/ios/sdk/src/JitsiMeetManager/PiPWindow/DragGestureController.swift +++ b/ios/sdk/src/picture-in-picture/DragGestureController.swift @@ -1,4 +1,18 @@ -// Copyright © 2018 Jitsi. All rights reserved. +/* + * Copyright @ 2017-present Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ final class DragGestureController { @@ -7,7 +21,8 @@ final class DragGestureController { private var frameBeforeDragging: CGRect = CGRect.zero private weak var view: UIView? private lazy var panGesture: UIPanGestureRecognizer = { - return UIPanGestureRecognizer(target: self, action: #selector(handlePan(gesture:))) + return UIPanGestureRecognizer(target: self, + action: #selector(handlePan(gesture:))) }() func startDragListener(inView view: UIView) { @@ -47,7 +62,8 @@ final class DragGestureController { let distanceMagnitude = magnitude(vector: distance) let velocityMagnitude = magnitude(vector: velocity) let animationDuration = 0.5 - let initialSpringVelocity = velocityMagnitude / distanceMagnitude / CGFloat(animationDuration) + let initialSpringVelocity = + velocityMagnitude / distanceMagnitude / CGFloat(animationDuration) frame.origin = CGPoint(x: finalPos.x, y: finalPos.y) @@ -91,8 +107,14 @@ final class DragGestureController { goUp = location.y < bounds.midY } - let finalPosX: CGFloat = goLeft ? adjustedBounds.origin.x : bounds.size.width - insets.right - currentSize.width - let finalPosY: CGFloat = goUp ? adjustedBounds.origin.y : bounds.size.height - insets.bottom - currentSize.height + let finalPosX: CGFloat = + goLeft + ? adjustedBounds.origin.x + : bounds.size.width - insets.right - currentSize.width + let finalPosY: CGFloat = + goUp + ? adjustedBounds.origin.y + : bounds.size.height - insets.bottom - currentSize.height return CGPoint(x: finalPosX, y: finalPosY) } @@ -101,4 +123,3 @@ final class DragGestureController { return sqrt(pow(vector.x, 2) + pow(vector.y, 2)) } } - diff --git a/ios/sdk/src/picture-in-picture/JitsiMeetPresentationCoordinator.swift b/ios/sdk/src/picture-in-picture/JitsiMeetPresentationCoordinator.swift new file mode 100644 index 000000000..404bf4c01 --- /dev/null +++ b/ios/sdk/src/picture-in-picture/JitsiMeetPresentationCoordinator.swift @@ -0,0 +1,97 @@ +/* + * Copyright @ 2017-present Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +/// Coordinates the presentation of JitsiMeetViewController inside of +/// an external window that can be resized and dragged with custom PiP mode +open class JitsiMeetPresentationCoordinator: NSObject { + + fileprivate let meetViewController: JitsiMeetViewController + fileprivate let meetWindow: PiPWindow + + public init(meetViewController: JitsiMeetViewController? = nil, + meetWindow: PiPWindow? = nil) { + self.meetViewController = meetViewController ?? JitsiMeetViewController() + self.meetWindow = meetWindow ?? PiPWindow(frame: UIScreen.main.bounds) + + super.init() + + configureMeetWindow() + configureMeetViewController() + } + + public func jitsiMeetView() -> JitsiMeetView { + return meetViewController.jitsiMeetView + } + + public func show() { + meetWindow.show() + } + + public func hide() { + meetWindow.hide() + } + + deinit { + cleanUp() + } + + // MARK: - helpers + + fileprivate func cleanUp() { + // TODO: more clean up work on this + + meetWindow.isHidden = true + meetWindow.stopDragGesture() + } + + private func configureMeetViewController() { + meetViewController.jitsiMeetView.pictureInPictureEnabled = true + meetViewController.delegate = self + } + + private func configureMeetWindow() { + meetWindow.backgroundColor = .clear + meetWindow.windowLevel = UIWindowLevelStatusBar + 100 + meetWindow.rootViewController = self.meetViewController + } +} + +extension JitsiMeetPresentationCoordinator: JitsiMeetViewControllerDelegate { + + open func performPresentationUpdate(to: JitsiMeetPresentationUpdate) { + switch to { + case .enterPictureInPicture: + meetWindow.enterPictureInPicture() + case .traitChange: + // resize to full screen if rotation happens + if meetWindow.isInPiP { + meetWindow.exitPictureInPicture() + } + } + } + + open func conferenceStarted() { + if meetWindow.isHidden { + meetWindow.show() + } + } + + open func conferenceEnded(didFail: Bool) { + cleanUp() + } +} diff --git a/ios/sdk/src/JitsiMeetManager/JitsiMeetViewController.swift b/ios/sdk/src/picture-in-picture/JitsiMeetViewController.swift similarity index 63% rename from ios/sdk/src/JitsiMeetManager/JitsiMeetViewController.swift rename to ios/sdk/src/picture-in-picture/JitsiMeetViewController.swift index a9f471a39..c9232e428 100644 --- a/ios/sdk/src/JitsiMeetManager/JitsiMeetViewController.swift +++ b/ios/sdk/src/picture-in-picture/JitsiMeetViewController.swift @@ -1,21 +1,42 @@ -// Copyright © 2018 Jitsi. All rights reserved. +/* + * Copyright @ 2017-present Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ public enum JitsiMeetPresentationUpdate { + /// The conference wants to enter Picture-in-Picture + case enterPictureInPicture + /// A system traitCollectionChange (usually screen rotation) case traitChange - /// Meeting wants to enter PiP mode - case enterPiP } public protocol JitsiMeetViewControllerDelegate: class { - /// Notifies a change of the meeting presentation style. + /// Notifies a change of the conference presentation style. /// /// - Parameter to: The presentation state that will be changed to func performPresentationUpdate(to: JitsiMeetPresentationUpdate) - func meetingStarted() - func meetingEnded(wasFailure: Bool) + + /// The conference started + func conferenceStarted() + + /// The conference ended + /// + /// - Parameter didFail: The reason of ending the conference + func conferenceEnded(didFail: Bool) } /// Wrapper ViewController of a JitsiMeetView @@ -52,7 +73,7 @@ extension JitsiMeetViewController: JitsiMeetViewDelegate { open func conferenceJoined(_ data: [AnyHashable : Any]!) { DispatchQueue.main.async { - self.delegate?.meetingStarted() + self.delegate?.conferenceStarted() } } @@ -62,13 +83,13 @@ extension JitsiMeetViewController: JitsiMeetViewDelegate { open func conferenceLeft(_ data: [AnyHashable : Any]!) { DispatchQueue.main.async { - self.delegate?.meetingEnded(wasFailure: true) + self.delegate?.conferenceEnded(didFail: true) } } open func conferenceFailed(_ data: [AnyHashable : Any]!) { DispatchQueue.main.async { - self.delegate?.meetingEnded(wasFailure: true) + self.delegate?.conferenceEnded(didFail: true) } } @@ -78,7 +99,7 @@ extension JitsiMeetViewController: JitsiMeetViewDelegate { open func enterPicture(inPicture data: [AnyHashable : Any]!) { DispatchQueue.main.async { - self.delegate?.performPresentationUpdate(to: .enterPiP) + self.delegate?.performPresentationUpdate(to: .enterPictureInPicture) } } } diff --git a/ios/sdk/src/JitsiMeetManager/PiPWindow/PiPWindow.swift b/ios/sdk/src/picture-in-picture/PiPWindow.swift similarity index 65% rename from ios/sdk/src/JitsiMeetManager/PiPWindow/PiPWindow.swift rename to ios/sdk/src/picture-in-picture/PiPWindow.swift index 424859267..1d9f8a02e 100644 --- a/ios/sdk/src/JitsiMeetManager/PiPWindow/PiPWindow.swift +++ b/ios/sdk/src/picture-in-picture/PiPWindow.swift @@ -1,11 +1,28 @@ -// Copyright © 2018 Jitsi. All rights reserved. +/* + * Copyright @ 2017-present Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ /// A window that allows its root view controller to be presented /// in full screen or in a custom Picture in Picture mode open class PiPWindow: UIWindow { /// Limits the boundries of root view position on screen when minimized - public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25, left: 5, bottom: 5, right: 5) { + public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25, + left: 5, + bottom: 5, + right: 5) { didSet { dragController.insets = dragBoundInsets } @@ -24,7 +41,8 @@ open class PiPWindow: UIWindow { private var exitPiPButton: UIButton? /// Help out to bubble up the gesture detection outside of the rootVC frame - open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + open override func point(inside point: CGPoint, + with event: UIEvent?) -> Bool { guard let vc = rootViewController else { return super.point(inside: point, with: event) } @@ -36,35 +54,24 @@ open class PiPWindow: UIWindow { if self.isHidden || self.alpha < 1 { self.isHidden = false self.alpha = 0 - - UIView.animate( - withDuration: 0.1, - delay: 0, - options: .beginFromCurrentState, - animations: { - self.alpha = 1 - }, - completion: nil) + animateTransition { + self.alpha = 1 + } } } /// animate out the window open func hide() { if !self.isHidden || self.alpha > 0 { - UIView.animate( - withDuration: 0.1, - delay: 0, - options: .beginFromCurrentState, - animations: { - self.alpha = 0 - self.isHidden = true - }, - completion: nil) + animateTransition { + self.alpha = 0 + self.isHidden = true + } } } /// Resize the root view to PiP mode - open func goToPiP() { + open func enterPictureInPicture() { guard let view = rootViewController?.view else { return } isInPiP = true animateRootViewChange() @@ -72,13 +79,15 @@ open class PiPWindow: UIWindow { dragController.insets = dragBoundInsets // add single tap gesture recognition for displaying exit PiP UI - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(toggleExitPiP)) + let exitSelector = #selector(toggleExitPiP) + let tapGestureRecognizer = UITapGestureRecognizer(target: self, + action: exitSelector) self.tapGestureRecognizer = tapGestureRecognizer view.addGestureRecognizer(tapGestureRecognizer) } /// Resize the root view to full screen - open func goToFullScreen() { + open func exitPictureInPicture() { isInPiP = false animateRootViewChange() dragController.stopDragListener() @@ -88,7 +97,8 @@ open class PiPWindow: UIWindow { exitPiPButton = nil // remove gesture - tapGestureRecognizer?.removeTarget(self, action: #selector(toggleExitPiP)) + let exitSelector = #selector(toggleExitPiP) + tapGestureRecognizer?.removeTarget(self, action: exitSelector) tapGestureRecognizer = nil } @@ -98,8 +108,11 @@ open class PiPWindow: UIWindow { } /// Customize the presentation of exit pip button - open func configureExitPiPButton(target: Any, action: Selector) -> UIButton { - let buttonImage = UIImage.init(named: "image-resize", in: Bundle(for: type(of: self)), compatibleWith: nil) + open func configureExitPiPButton(target: Any, + action: Selector) -> UIButton { + let buttonImage = UIImage.init(named: "image-resize", + in: Bundle(for: type(of: self)), + compatibleWith: nil) let button = UIButton(type: .custom) let size: CGSize = CGSize(width: 44, height: 44) button.setImage(buttonImage, for: .normal) @@ -143,7 +156,9 @@ open class PiPWindow: UIWindow { if exitPiPButton == nil { // show button - let button = configureExitPiPButton(target: self, action: #selector(exitPiP)) + let exitSelector = #selector(exitPictureInPicture) + let button = configureExitPiPButton(target: self, + action: exitSelector) view.addSubview(button) exitPiPButton = button @@ -152,10 +167,19 @@ open class PiPWindow: UIWindow { exitPiPButton?.removeFromSuperview() exitPiPButton = nil } - } @objc private func exitPiP() { - goToFullScreen() + exitPictureInPicture() + } + + // MARK: - Animation transition + + private func animateTransition(animations: @escaping () -> Void) { + UIView.animate(withDuration: 0.1, + delay: 0, + options: .beginFromCurrentState, + animations: animations, + completion: nil) } } diff --git a/ios/sdk/image-resize@2x.png b/ios/sdk/src/picture-in-picture/image-resize@2x.png similarity index 100% rename from ios/sdk/image-resize@2x.png rename to ios/sdk/src/picture-in-picture/image-resize@2x.png diff --git a/ios/sdk/image-resize@3x.png b/ios/sdk/src/picture-in-picture/image-resize@3x.png similarity index 100% rename from ios/sdk/image-resize@3x.png rename to ios/sdk/src/picture-in-picture/image-resize@3x.png