Skip to content

Commit

Permalink
feat: incorporate Monarch support in the iOS Demo app (#27)
Browse files Browse the repository at this point in the history
* feat: incorporate Monarch support in the iOS Demo app (#24)

* feat: use WebRTC supported SDK to open door

make IncomingCallViewController conform to BMXCall.IncomingCallUIInputs
call new API BMXCallKit.shared.processCall(guid:callType:incomingCallPresenter:completion) to process incoming calls.

* refactor: remove some DispatchQueue.main.async

use DispatchQueue.main.async in our SDK instead to make users easier to use the SDK.

* refactor: remove extension for BMXCallDelegate

* refactor: pass unwrapped incomingViewController

pass unwrapped incomingViewController (with guard let) to processCall

Refs # NT-458

* refactor: refactor code to handle more call scenarios (#25)

* feat: use WebRTC supported SDK to open door

make IncomingCallViewController conform to BMXCall.IncomingCallUIInputs
call new API BMXCallKit.shared.processCall(guid:callType:incomingCallPresenter:completion) to process incoming calls.

* refactor: remove some DispatchQueue.main.async

use DispatchQueue.main.async in our SDK instead to make users easier to use the SDK.

* refactor: remove extension for BMXCallDelegate

* refactor: pass unwrapped incomingViewController

pass unwrapped incomingViewController (with guard let) to processCall

* refactor: add IncomingCallPresenter

only reportNewIncomingCall for the intial call.
show IncomingCallViewContrller after users press the accept button

* fix: review fixes

call CallsService.shared.endCurrentCallKitCall() instead of BMXCallKit.shared.endCall() in hangUpAction

remove function handleCallEnded

* refactor: use CallStatusHandler to handel handle call status changes

* fix: review fixes

remove CallEndReason

* fix: review fixes

use callConnected  instead of handleCallConnected
use callAccepted  instead of handleCallAccepted

Refs # NT-458

* fix: simplify Podfile

* feat: mark Intercom and Keypads for devices (#26)

add subtitle for DoorTableViewCell to show the current device is Intercom or KeyPad

add setup(by device: DeviceModel) in DoorTableViewCell

* chore: import BMXCore 2.1.0 and BMXCall 2.1.0
  • Loading branch information
yingtao-butterflymx committed Nov 17, 2021
1 parent 210eb92 commit 68e63ae
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 169 deletions.
24 changes: 18 additions & 6 deletions ButterflyMX Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
1A8DF4A2225CBAF000AB0393 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8DF4A1225CBAF000AB0393 /* Colors.swift */; };
1A8DF4A3225CBAF500AB0393 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8DF4A1225CBAF000AB0393 /* Colors.swift */; };
1AA6FBF9219B2A78002F7168 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1AA6FBF8219B2A78002F7168 /* Foundation.framework */; };
9CAE16F0271FB43700E6E5DC /* IncomingCallPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CAE16EF271FB43700E6E5DC /* IncomingCallPresenter.swift */; };
9CAE16F1271FB43B00E6E5DC /* IncomingCallPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CAE16EF271FB43700E6E5DC /* IncomingCallPresenter.swift */; };
9CAE16F62720BC4D00E6E5DC /* CallStatusHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CAE16F52720BC4D00E6E5DC /* CallStatusHandler.swift */; };
9CAE16F72720BC4D00E6E5DC /* CallStatusHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CAE16F52720BC4D00E6E5DC /* CallStatusHandler.swift */; };
C3689694234CD51F003DED8D /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3689693234CD51F003DED8D /* AVFoundation.framework */; };
C3C2322C21F5C6B500A84483 /* (null) in Sources */ = {isa = PBXBuildFile; };
C3C2322D21F5C6B500A84483 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1C1317219ADCEE003449F8 /* LoginViewController.swift */; };
Expand Down Expand Up @@ -93,6 +97,8 @@
1A8DF4A1225CBAF000AB0393 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
1AA6FBF8219B2A78002F7168 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1ACCD9B121EF73FB00E97924 /* ButterflyMX Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ButterflyMX Demo.entitlements"; sourceTree = "<group>"; };
9CAE16EF271FB43700E6E5DC /* IncomingCallPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallPresenter.swift; sourceTree = "<group>"; };
9CAE16F52720BC4D00E6E5DC /* CallStatusHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallStatusHandler.swift; sourceTree = "<group>"; };
C3689693234CD51F003DED8D /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
C3C2324121F5C6B500A84483 /* ButterflyMX Demo Internal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ButterflyMX Demo Internal.app"; sourceTree = BUILT_PRODUCTS_DIR; };
CE11560B1A4691DFA2BADB9A /* Pods-ButterflyMX Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ButterflyMX Demo.release.xcconfig"; path = "Target Support Files/Pods-ButterflyMX Demo/Pods-ButterflyMX Demo.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -138,7 +144,9 @@
1A05268421F0EB5F00F6D373 /* Logic */ = {
isa = PBXGroup;
children = (
9CAE16EF271FB43700E6E5DC /* IncomingCallPresenter.swift */,
1A05267421F0A60700F6D373 /* CallsService.swift */,
9CAE16F52720BC4D00E6E5DC /* CallStatusHandler.swift */,
);
path = Logic;
sourceTree = "<group>";
Expand Down Expand Up @@ -390,6 +398,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9CAE16F62720BC4D00E6E5DC /* CallStatusHandler.swift in Sources */,
1A7D091021F7166F00AA1999 /* AccountTableViewController.swift in Sources */,
1A8DF4A2225CBAF000AB0393 /* Colors.swift in Sources */,
1A1C1318219ADCEE003449F8 /* LoginViewController.swift in Sources */,
Expand All @@ -401,6 +410,7 @@
1A1C1303219AD954003449F8 /* AppDelegate.swift in Sources */,
1A05267521F0A60700F6D373 /* CallsService.swift in Sources */,
1A1C1316219ADC82003449F8 /* Extension.swift in Sources */,
9CAE16F0271FB43700E6E5DC /* IncomingCallPresenter.swift in Sources */,
1A8DF49F225CB26500AB0393 /* DoorsTableViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -413,6 +423,8 @@
1A8DF4A0225CB26800AB0393 /* DoorsTableViewController.swift in Sources */,
C3C2322D21F5C6B500A84483 /* LoginViewController.swift in Sources */,
1A2CD59121F6310900515F51 /* AccountTableViewController.swift in Sources */,
9CAE16F72720BC4D00E6E5DC /* CallStatusHandler.swift in Sources */,
9CAE16F1271FB43B00E6E5DC /* IncomingCallPresenter.swift in Sources */,
C3C2322E21F5C6B500A84483 /* (null) in Sources */,
C3C2322F21F5C6B500A84483 /* GradientView.swift in Sources */,
1A715B10225632290002772C /* UnitsViewController.swift in Sources */,
Expand Down Expand Up @@ -495,7 +507,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
Expand All @@ -521,7 +533,7 @@
"PJ_AUTOCONF=1",
);
INFOPLIST_FILE = "$(SRCROOT)/ButterflyMX Demo/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -550,7 +562,7 @@
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/ButterflyMX Demo/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -627,7 +639,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
Expand All @@ -653,7 +665,7 @@
"PJ_AUTOCONF=1",
);
INFOPLIST_FILE = "$(SRCROOT)/ButterflyMX Demo/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -682,7 +694,7 @@
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/ButterflyMX Demo/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
2 changes: 2 additions & 0 deletions ButterflyMX Demo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window!.rootViewController = loginViewController
}

CallsService.shared.window = window
CallsService.shared.setupVoipPush()
requestAccessMicCamera(callback: { status in
print("User media permission status \(status.rawValue)")
})

return true
}

Expand Down
129 changes: 68 additions & 61 deletions ButterflyMX Demo/Controllers/IncomingCallViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import BMXCall
import BMXCore

class IncomingCallViewController: UIViewController {

fileprivate var cameraIsEnabled = false
fileprivate var startTime = 0.0
fileprivate var previewTime = 0.0
Expand Down Expand Up @@ -49,63 +48,56 @@ class IncomingCallViewController: UIViewController {
@IBAction func micAction(_ sender: UIButton) {
if microphoneIsEnabled {
microphoneIsEnabled = false
sender.setImage(UIImage(named: "button_mute_active"), for: .normal)
BMXCallKit.shared.muteMic()
print("mute mic")
} else {
microphoneIsEnabled = true
sender.setImage(UIImage(named: "button_mute"), for: .normal)
BMXCallKit.shared.unmuteMic()
print("unmute mic")
}
}

@IBAction func hangUpAction(_ sender: Any) {
BMXCallKit.shared.endCall()
CallsService.shared.endCurrentCallKitCall()
}

@IBAction func openDoorAction(_ sender: Any) {
BMXCallKit.shared.openDoor()
self.alert(message: "Door is open!")
BMXCallKit.shared.openDoor() { result in
if result {
self.alert(message: "Door is open!")
} else {
self.alert(message: "Failed to open the door!")
}
}
}

@IBAction func cameraAction(_ sender: UIButton) {
if self.cameraIsEnabled {
sender.setImage(UIImage(named: "button_camera"), for: .normal)
self.selfVideoView.isHidden = true
self.cameraIsEnabled = false
BMXCallKit.shared.hideOutgoingVideo()
} else {
self.selfVideoView.isHidden = false
self.cameraIsEnabled = true
BMXCallKit.shared.showOutgoingVideo()
sender.setImage(UIImage(named: "button_camera_active"), for: .normal)
}
}

@IBAction func speackerAction(_ sender: UIButton) {
sender.isUserInteractionEnabled = false
speakerIsEnabled = !speakerIsEnabled

sender.setImage(UIImage(named: speakerIsEnabled ? "button_speaker_active" : "button_speaker"), for: .normal)
DispatchQueue.global(qos: .default).async {
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(self.speakerIsEnabled ? .speaker : .none)
DispatchQueue.main.async {
sender.isUserInteractionEnabled = true
}
} catch {
print("Divert audio to Speaker error: \(error)")
}
@IBAction func speakerAction(_ sender: UIButton) {
if speakerIsEnabled {
speakerIsEnabled = false
BMXCallKit.shared.turnOffSpeaker()
} else {
speakerIsEnabled = true
BMXCallKit.shared.turnOnSpeaker()
}
}

//MARK: - Methods

override func viewDidLoad() {
super.viewDidLoad()
BMXCallKit.shared.delegate = self
setupUIData()
fullScreenButton.isHidden = true
}

static func initViewController() -> IncomingCallViewController {
Expand All @@ -115,27 +107,29 @@ class IncomingCallViewController: UIViewController {
}
return incomingCallViewController
}

private func setupUIData() {
if speakerIsEnabled {
speakerButton.setImage(UIImage(named: "button_speaker_active"), for: .normal)
}

guard let call = BMXCallKit.shared.activeCall?.callDetails else { return }
guard let callAttributes = BMXCallKit.shared.activeCall?.attributes else { return }

if let image = call.mediumUrl {
if let image = callAttributes.mediumUrl {
let imageData = NSData(contentsOf: URL(string: image)!)
self.imagePreview.image = UIImage(data: imageData! as Data)
}

timer = Timer.scheduledTimer(timeInterval: 1, target:self, selector: #selector(updateTime), userInfo: nil, repeats: true)

callTimeLabel.text = "00:00"
callTypeLabel.text = call.getTitle()
panelNameLabel.text = call.panelName
callTypeLabel.text = callAttributes.getTitle()
panelNameLabel.text = callAttributes.panelName
blurView.addBlurView()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
UIView.animate(withDuration: 1, delay: 0.5, options: .curveEaseOut, animations: {
self.blurView.alpha = 0.0
}, completion: { (_) in
}, completion: { _ in
self.blurView.removeFromSuperview()
})
}
Expand All @@ -157,43 +151,56 @@ class IncomingCallViewController: UIViewController {
: videoView.bounds.size.width / size.width
return CGSize(width: size.width * k, height: size.height * k)
}

func setIncomingVideo( _ video: UIView) {
incomingView = video
video.bounds.size = self.getVideoSize(basedOnOriginalSize: video.bounds.size, forFullscreen: true)
video.center = self.videoView.center
video.contentMode = .scaleAspectFill
video.clipsToBounds = true
imagePreview.isHidden = true
spinner.stopAnimating()
videoView.addSubview(video)
}
}

extension IncomingCallViewController {
func updateSpeakerControlStatus(enabled: Bool) {
speakerButton.setImage(UIImage(named: enabled ? "button_speaker_active" : "button_speaker"), for: .normal)
}

func updateMicrophoneControlStatus(enabled: Bool) {
micButton.setImage(UIImage(named: enabled ? "button_mute" : "button_mute_active"), for: .normal)
}

func updateCameraControlStatus(enabled: Bool) {
cameraButton.setImage(UIImage(named: enabled ? "button_camera_active" : "button_camera"), for: .normal)
}

extension IncomingCallViewController: BMXCallDelegate {

func outgoingVideoStarted(video: UIView) -> CGSize? {
selfVideoView.addSubview(video)
func setupWaitingForAnsweringCallUI() {
setupUIData()
}

func getInputVideoViewSize() -> CGSize {
return view?.bounds.size ?? .zero
}

func getOutputVideoViewSize() -> CGSize {
return selfVideoView.bounds.size
}

func incomingVideoStarted(video: UIView) {
setIncomingVideo(video)
acceptedCallContainerView.isHidden = false

func displayIncomingVideo(from view: UIView) {
incomingView = view
view.bounds.size = getVideoSize(basedOnOriginalSize: view.bounds.size, forFullscreen: true)
view.center = videoView.center
view.contentMode = .scaleAspectFill
view.clipsToBounds = true

imagePreview.isHidden = true
videoView.addSubview(view)
}

func callEnded(_ call: CallStatus) {
DispatchQueue.main.async {
CallsService.shared.endCurrentCallKitCall()
self.dismiss(animated: true, completion: nil)
}

func displayOutgoingVideo(from view: UIView) {
selfVideoView.addSubview(view)
}

func callCanceled(_ call: CallStatus, reason: CallCancelReason) {
DispatchQueue.main.async {
CallsService.shared.endCurrentCallKitCall()
self.dismiss(animated: true, completion: nil)
}

func handleCallConnected() {
spinner.stopAnimating()
fullScreenButton.isHidden = false
acceptedCallContainerView.isHidden = false
}

func handleCallAccepted(from call: Call, usingCallKit: Bool) {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(timeInterval: 1, target:self, selector: #selector(self.updateTime), userInfo: nil, repeats: true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@ class DoorTableViewCell: UITableViewCell {
@IBOutlet weak var doorNameLabel: UILabel!
@IBOutlet weak var statusLabel: UILabel!
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!

@IBOutlet weak var deviceTypeLabel: UILabel!

override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}

func setup(by device: DeviceModel) {
doorNameLabel.text = device.name ?? ""
if device.type == "keypads" {
deviceTypeLabel.text = "Keypad"
} else {
deviceTypeLabel.text = "Intercom"
}
}

func pleaseWait() {
statusLabel.isHidden = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class UnitTableViewCell: UITableViewCell {
return
}

let ngrokId = "6036eb46088a"
let ngrokId = "bdab-2601-801-201-bad0-49ac-3393-470b-1ebd"
let webhookUrl = "http:https://\(ngrokId).ngrok.io/webhook/?token=\(deviceToken)&type=voip"

SVProgressHUD.show()
Expand Down
Loading

0 comments on commit 68e63ae

Please sign in to comment.