Commit b82c2bf5 authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Andreas Traczyk

call: refine call options

This patch adds the next changes:
- refactor call screen UI
- introduce audio service
- add switch speakerphone
- add headset support

Change-Id: Ie17df97eb64b6d0f6912451e69d67ce8647b4c38
parent e455c9bf
......@@ -114,6 +114,9 @@
0E49097A1FEAC9E1005CAA50 /* CallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4909791FEAC9E1005CAA50 /* CallViewController.swift */; };
0E49097C1FEACA4B005CAA50 /* CallViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49097B1FEACA4B005CAA50 /* CallViewModel.swift */; };
0E6949791FA7E71C0029B60A /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6949781FA7E71C0029B60A /* BaseViewController.swift */; };
0E7CF4DB20164B6700CD967D /* ButtonsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7CF4DA20164B6700CD967D /* ButtonsContainerView.swift */; };
0E7CF4DD20165BFB00CD967D /* ButtonsContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7CF4DC20165BFB00CD967D /* ButtonsContainerViewModel.swift */; };
0E7CF4DF2017918300CD967D /* ButtonsContainerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E7CF4DE2017918300CD967D /* ButtonsContainerView.xib */; };
0E983E6E1FC77C3E0082103E /* ConversationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E983E6D1FC77C3E0082103E /* ConversationModel.swift */; };
0E9D84491FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9D84481FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift */; };
0E9D844B1FA7DBAA00C561EB /* ContactRequestTabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9D844A1FA7DBAA00C561EB /* ContactRequestTabBarItem.swift */; };
......@@ -241,6 +244,8 @@
62AA15BF1FFC36840064A063 /* VideoAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 62AA15BE1FFC36840064A063 /* VideoAdapter.mm */; };
62AA15C31FFC39C80064A063 /* VideoAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62AA15C21FFC39C80064A063 /* VideoAdapterDelegate.swift */; };
62AA15CA1FFD3D7E0064A063 /* VideoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62AA15C91FFD3D7E0064A063 /* VideoService.swift */; };
62AF685E201A61FF003AA9E8 /* AudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62AF685D201A61FF003AA9E8 /* AudioService.swift */; };
62AF6862201A66CF003AA9E8 /* AudioAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 62AF6861201A66CF003AA9E8 /* AudioAdapter.mm */; };
62DFAB2C1F9FF030002D6F9C /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62DFAB2B1F9FF030002D6F9C /* Reachability.framework */; };
62DFAB2E1F9FF0D0002D6F9C /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */; };
62E55B6D1F758E6F00D3FEF4 /* String+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E55B6C1F758E6F00D3FEF4 /* String+Helpers.swift */; };
......@@ -375,6 +380,9 @@
0E4909791FEAC9E1005CAA50 /* CallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = "<group>"; };
0E49097B1FEACA4B005CAA50 /* CallViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewModel.swift; sourceTree = "<group>"; };
0E6949781FA7E71C0029B60A /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; };
0E7CF4DA20164B6700CD967D /* ButtonsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsContainerView.swift; sourceTree = "<group>"; };
0E7CF4DC20165BFB00CD967D /* ButtonsContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsContainerViewModel.swift; sourceTree = "<group>"; };
0E7CF4DE2017918300CD967D /* ButtonsContainerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ButtonsContainerView.xib; sourceTree = "<group>"; };
0E983E6D1FC77C3E0082103E /* ConversationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationModel.swift; sourceTree = "<group>"; };
0E9D84481FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTabBarItemViewModel.swift; sourceTree = "<group>"; };
0E9D844A1FA7DBAA00C561EB /* ContactRequestTabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestTabBarItem.swift; sourceTree = "<group>"; };
......@@ -561,6 +569,9 @@
62AD0C281FE03FF600BEA1F6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
62AD0C2A1FE054DD00BEA1F6 /* zh-Hans-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans-CN"; path = "zh-Hans-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
62AD0C2B1FE0557D00BEA1F6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
62AF685D201A61FF003AA9E8 /* AudioService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioService.swift; sourceTree = "<group>"; };
62AF6861201A66CF003AA9E8 /* AudioAdapter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AudioAdapter.mm; sourceTree = "<group>"; };
62AF6863201A66F0003AA9E8 /* AudioAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioAdapter.h; sourceTree = "<group>"; };
62DFAB2B1F9FF030002D6F9C /* Reachability.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reachability.framework; path = Carthage/Build/iOS/Reachability.framework; sourceTree = "<group>"; };
62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
62E55B6C1F758E6F00D3FEF4 /* String+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Helpers.swift"; sourceTree = "<group>"; };
......@@ -730,6 +741,7 @@
0E49096D1FEAC0DE005CAA50 /* CallsService.swift */,
62AA15C21FFC39C80064A063 /* VideoAdapterDelegate.swift */,
62AA15C91FFD3D7E0064A063 /* VideoService.swift */,
62AF685D201A61FF003AA9E8 /* AudioService.swift */,
);
path = Services;
sourceTree = "<group>";
......@@ -767,6 +779,8 @@
0E4909691FEAB156005CAA50 /* CallsAdapter.mm */,
62AA15BD1FFC366D0064A063 /* VideoAdapter.h */,
62AA15BE1FFC36840064A063 /* VideoAdapter.mm */,
62AF6861201A66CF003AA9E8 /* AudioAdapter.mm */,
62AF6863201A66F0003AA9E8 /* AudioAdapter.h */,
);
path = Bridging;
sourceTree = "<group>";
......@@ -967,6 +981,9 @@
0E4909741FEAC943005CAA50 /* CallViewController.storyboard */,
0E4909791FEAC9E1005CAA50 /* CallViewController.swift */,
0E49097B1FEACA4B005CAA50 /* CallViewModel.swift */,
0E7CF4DA20164B6700CD967D /* ButtonsContainerView.swift */,
0E7CF4DC20165BFB00CD967D /* ButtonsContainerViewModel.swift */,
0E7CF4DE2017918300CD967D /* ButtonsContainerView.xib */,
);
path = Calls;
sourceTree = "<group>";
......@@ -1504,6 +1521,7 @@
1A5DC03E1F35678D0075E8EF /* ContactRequestsViewController.storyboard in Resources */,
0EB1A5CF1F8EBE03009923E2 /* DeviceCell.xib in Resources */,
1A2D18B31F2915C500B2C785 /* ConversationViewController.storyboard in Resources */,
0E7CF4DF2017918300CD967D /* ButtonsContainerView.xib in Resources */,
1A2D18A01F27A6D600B2C785 /* LinkDeviceViewController.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
......@@ -1638,6 +1656,7 @@
56BBC99F1ED714CB00CDAF8B /* MessagesAdapter.mm in Sources */,
0E49096A1FEAB156005CAA50 /* CallsAdapter.mm in Sources */,
1A2D18A61F27F7A400B2C785 /* UIViewController+Rx.swift in Sources */,
0E7CF4DB20164B6700CD967D /* ButtonsContainerView.swift in Sources */,
0E49097A1FEAC9E1005CAA50 /* CallViewController.swift in Sources */,
1A5DC0241F3564360075E8EF /* ContactRequestModel.swift in Sources */,
0E4909701FEAC1C6005CAA50 /* CallModel.swift in Sources */,
......@@ -1674,6 +1693,7 @@
1A2D189A1F2642C000B2C785 /* NotificationCenter+Ring.swift in Sources */,
1A2D18FC1F292DAD00B2C785 /* ConversationCell.swift in Sources */,
0E48F9D31FDF150700D6CC08 /* ContactRequestManager.swift in Sources */,
0E7CF4DD20165BFB00CD967D /* ButtonsContainerViewModel.swift in Sources */,
1A5DC0371F35675E0075E8EF /* ContactRequestCell.swift in Sources */,
1A20417C1F1E56FF00C08435 /* WelcomeViewModel.swift in Sources */,
1A5DC03D1F35678D0075E8EF /* ContactRequestItem.swift in Sources */,
......@@ -1682,6 +1702,7 @@
1A0C4EE31F1D673600550433 /* InjectionBag.swift in Sources */,
564C44641E943E1E000F92B1 /* NameRegistrationAdapterDelegate.swift in Sources */,
1A2D18AA1F29131900B2C785 /* ConversationsCoordinator.swift in Sources */,
62AF685E201A61FF003AA9E8 /* AudioService.swift in Sources */,
043999F71D1C2D9D00E99CD9 /* AppDelegate.swift in Sources */,
1A2041861F1EA19600C08435 /* CreateAccountViewController.swift in Sources */,
0EDCC8601F98150500B121D7 /* UIView+Rx.swift in Sources */,
......@@ -1711,6 +1732,7 @@
0273C3081E0C68BF00CF00BA /* DesignableButton.swift in Sources */,
1A5DC0321F3566140075E8EF /* ConversationSection.swift in Sources */,
1A2D18C41F29180700B2C785 /* ConfigKeyModel.swift in Sources */,
62AF6862201A66CF003AA9E8 /* AudioAdapter.mm in Sources */,
1A20417A1F1E547F00C08435 /* Stateable.swift in Sources */,
1A2D18F51F292D7200B2C785 /* MessageCellReceived.swift in Sources */,
56BBC9BC1ED7161200CDAF8B /* Date+Helpers.swift in Sources */,
......
......@@ -38,6 +38,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private let presenceService = PresenceService(withPresenceAdapter: PresenceAdapter())
private let callService = CallsService(withCallsAdapter: CallsAdapter())
private let videoService = VideoService(withVideoAdapter: VideoAdapter())
private let audioService = AudioService(withAudioAdapter: AudioAdapter())
private let networkService = NetworkService()
private var conversationManager: ConversationsManager?
private var contactRequestManager: ContactRequestManager?
......@@ -51,7 +52,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
withPresenceService: self.presenceService,
withNetworkService: self.networkService,
withCallService: self.callService,
withVideoService: self.videoService)
withVideoService: self.videoService,
withAudioService: self.audioService)
}()
private lazy var appCoordinator: AppCoordinator = {
return AppCoordinator(with: self.injectionBag)
......@@ -86,6 +88,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// start monitoring for network changes
self.networkService.monitorNetworkType()
// set device to headset if present
self.audioService.overrideAudioRoute(.override)
// themetize the app
Chameleon.setGlobalThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
Chameleon.setRingThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
......
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Foundation/Foundation.h>
@interface AudioAdapter : NSObject
- (void)setAudioOutputDevice:(NSInteger)index;
- (void)setAudioInputDevice:(NSInteger)index;
- (void)setAudioRingtoneDevice:(NSInteger)index;
@end
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "AudioAdapter.h"
#import "dring/configurationmanager_interface.h"
@implementation AudioAdapter
- (void)setAudioOutputDevice:(NSInteger)index {
DRing::setAudioOutputDevice((int32_t)index);
}
- (void)setAudioInputDevice:(NSInteger)index {
DRing::setAudioInputDevice((int32_t)index);
}
- (void)setAudioRingtoneDevice:(NSInteger)index {
DRing::setAudioRingtoneDevice((int32_t)index);
}
@end
......@@ -33,7 +33,7 @@
- (BOOL)holdCallWithId:(NSString*)callId;
- (BOOL)unholdCallWithId:(NSString*)callId;
- (NSString*)placeCallWithAccountId:(NSString*)accountId toRingId:(NSString*)ringId;
- (NSString*)placeCallWithAccountId:(NSString*)accountId toRingId:(NSString*)ringId details:(NSDictionary*)details;
- (NSDictionary<NSString*,NSString*>*)callDetailsWithCallId:(NSString*)callId;
- (NSArray<NSString*>*)calls;
- (void) sendTextMessageWithCallID:(NSString*)callId message:(NSDictionary*)message accountId:(NSString*)accountId sMixed:(bool)isMixed;
......
......@@ -155,8 +155,9 @@ static id <CallsAdapterDelegate> _delegate;
return unhold(std::string([callId UTF8String]));
}
- (NSString*)placeCallWithAccountId:(NSString*)accountId toRingId:(NSString*)ringId {
std::string callId = placeCall(std::string([accountId UTF8String]), std::string([ringId UTF8String]));
- (NSString*)placeCallWithAccountId:(NSString*)accountId toRingId:(NSString*)ringId details:(NSDictionary*)details {
std::string callId;
callId = placeCall(std::string([accountId UTF8String]), std::string([ringId UTF8String]), [Utils dictionnaryToMap:details]);
return [NSString stringWithUTF8String:callId.c_str()];
}
......
......@@ -34,6 +34,7 @@
#import "ContactsAdapter.h"
#import "PresenceAdapter.h"
#import "VideoAdapter.h"
#import "AudioAdapter.h"
#import <CommonCrypto/CommonCrypto.h>
#import <Contacts/Contacts.h>
#import "CallsAdapter.h"
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import UIKit
import Reusable
import RxSwift
class ButtonsContainerView: UIView, NibLoadable {
@IBOutlet var containerView: UIView!
@IBOutlet weak var container: UIView!
@IBOutlet weak var muteAudioButton: UIButton!
@IBOutlet weak var muteVideoButton: UIButton!
@IBOutlet weak var pauseCallButton: UIButton!
@IBOutlet weak var switchCameraButton: UIButton!
@IBOutlet weak var switchSpeakerButton: UIButton!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var containerHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var bottomSpaceConstraint: NSLayoutConstraint!
let disposeBag = DisposeBag()
var viewModel: ButtonsContainerViewModel? {
didSet {
self.viewModel?.observableCallOptions
.subscribe(onNext: { (callOptions) in
switch callOptions {
case .none:
self.withoutOptions()
case .optionsWithoutSpeakerphone:
self.optionsWithoutSpeaker()
case .optionsWithSpeakerphone:
self.optionsWithSpeaker()
}
}).disposed(by: self.disposeBag)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.commonInit()
}
override open func didMoveToWindow() {
super.didMoveToWindow()
self.cancelButton.backgroundColor = UIColor.red
if #available(iOS 11.0, *) {
guard let window = self.window else {
return
}
self.container.bottomAnchor.constraint(equalTo: window.bottomAnchor).isActive = true
}
}
func commonInit() {
Bundle.main.loadNibNamed("ButtonsContainerView", owner: self, options: nil)
addSubview(containerView)
containerView.frame = self.bounds
}
func withoutOptions() {
containerHeightConstraint.priority = UILayoutPriority(rawValue: 250.00)
bottomSpaceConstraint.priority = UILayoutPriority(rawValue: 999.00)
self.container.backgroundColor = UIColor.clear
muteAudioButton.isHidden = true
muteVideoButton.isHidden = true
pauseCallButton.isHidden = true
switchCameraButton.isHidden = true
switchSpeakerButton.isHidden = true
cancelButton.isHidden = false
}
func optionsWithSpeaker() {
containerHeightConstraint.priority = UILayoutPriority(rawValue: 999.00)
bottomSpaceConstraint.priority = UILayoutPriority(rawValue: 250.00)
self.container.backgroundColor = UIColor.black.withAlphaComponent(0.3)
muteAudioButton.isHidden = false
muteVideoButton.isHidden = false
pauseCallButton.isHidden = false
switchCameraButton.isHidden = false
switchSpeakerButton.isHidden = false
switchSpeakerButton.alpha = 1.00
switchSpeakerButton.isEnabled = true
cancelButton.isHidden = false
}
func optionsWithoutSpeaker() {
containerHeightConstraint.priority = UILayoutPriority(rawValue: 250.00)
bottomSpaceConstraint.priority = UILayoutPriority(rawValue: 999.00)
self.container.backgroundColor = UIColor.black.withAlphaComponent(0.3)
muteAudioButton.isHidden = false
muteVideoButton.isHidden = false
pauseCallButton.isHidden = false
switchCameraButton.isHidden = false
switchSpeakerButton.isHidden = false
switchSpeakerButton.alpha = 0.00
switchSpeakerButton.isEnabled = false
cancelButton.isHidden = false
}
}
This diff is collapsed.
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import RxSwift
enum CallOptions {
case none
case optionsWithoutSpeakerphone
case optionsWithSpeakerphone
}
class ButtonsContainerViewModel {
let callService: CallsService
let callID: String
let disposeBag = DisposeBag()
let avalaibleCallOptions = BehaviorSubject<CallOptions>(value: .none)
lazy var observableCallOptions: Observable<CallOptions> = {
return self.avalaibleCallOptions.asObservable()
}()
init(with callService: CallsService, callID: String) {
self.callService = callService
self.callID = callID
checkCallOptions()
}
private func checkCallOptions() {
let callIsActive: Observable<Bool> = {
self.callService.currentCall.filter({ call in
return call.state == .current && call.callId == self.callID
}).map({_ in
return true
})
}()
callIsActive.subscribe(onNext: { active in
if !active {
return
}
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
self.avalaibleCallOptions.onNext(.optionsWithoutSpeakerphone)
return
}
self.connectToSpeaker()
}).disposed(by: self.disposeBag)
}
private func connectToSpeaker() {
let speakerIsAvailable: Observable<Bool> = {
//TODO map to service
return Observable.just(true)
}()
speakerIsAvailable.subscribe(onNext: { available in
if available {
self.avalaibleCallOptions.onNext(.optionsWithSpeakerphone)
return
}
self.avalaibleCallOptions.onNext(.optionsWithoutSpeakerphone)
}).disposed(by: self.disposeBag)
}
}
......@@ -33,7 +33,6 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
@IBOutlet private weak var durationLabel: UILabel!
@IBOutlet private weak var infoBottomLabel: UILabel!
@IBOutlet private weak var cancelButton: UIButton!
@IBOutlet private weak var mainView: UIView!
//video screen
......@@ -42,16 +41,12 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
@IBOutlet private weak var capturedVideo: UIImageView!
@IBOutlet private weak var infoContainer: UIView!
@IBOutlet private weak var callProfileImage: UIImageView!
@IBOutlet private weak var audioOnlyImage: UIImageView!
@IBOutlet private weak var callNameLabel: UILabel!
@IBOutlet private weak var callInfoTimerLabel: UILabel!
@IBOutlet private weak var infoLabelConstraint: NSLayoutConstraint!
// call options buttons
@IBOutlet private weak var buttonsContainer: UIView!
@IBOutlet private weak var muteAudioButton: UIButton!
@IBOutlet private weak var muteVideoButton: UIButton!
@IBOutlet private weak var pauseCallButton: UIButton!
@IBOutlet private weak var switchCameraButton: UIButton!
@IBOutlet private weak var buttonsContainer: ButtonsContainerView!
var viewModel: CallViewModel!
......@@ -61,49 +56,95 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
private var task: DispatchWorkItem?
override var inputAccessoryView: UIView {
return self.buttonsContainer
}
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(screenTaped))
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(screenTapped))
self.mainView.addGestureRecognizer(tapGestureRecognizer)
self.setupUI()
self.infoContainer.backgroundColor = UIColor.black.withAlphaComponent(0.3)
self.setUpCallButtons()
self.setupBindings()
if self.viewModel.isAudioOnly {
self.showAllInfo()
}
func setupUI() {
self.cancelButton.backgroundColor = UIColor.red
self.infoContainer.backgroundColor = UIColor.black.withAlphaComponent(0.3)
self.buttonsContainer.backgroundColor = UIColor.black.withAlphaComponent(0.3)
UIDevice.current.isProximityMonitoringEnabled = self.viewModel.isAudioOnly
}
func setupBindings() {
func setUpCallButtons() {
self.buttonsContainer.viewModel = self.viewModel.containerViewModel
//bind actions
self.cancelButton.rx.tap
self.buttonsContainer.cancelButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.removeFromScreen()
self?.viewModel.cancelCall()
}).disposed(by: self.disposeBag)
self.muteAudioButton.rx.tap
self.buttonsContainer.muteAudioButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.viewModel.muteAudio()
self?.viewModel.toggleMuteAudio()
}).disposed(by: self.disposeBag)
self.muteVideoButton.rx.tap
if !(self.viewModel.call?.isAudioOnly ?? false) {
self.buttonsContainer.muteVideoButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.viewModel.muteVideo()
self?.viewModel.toggleMuteVideo()
}).disposed(by: self.disposeBag)
}
self.pauseCallButton.rx.tap
self.buttonsContainer.pauseCallButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.viewModel.pauseCall()
self?.viewModel.togglePauseCall()
}).disposed(by: self.disposeBag)
self.switchCameraButton.rx.tap
self.buttonsContainer.switchCameraButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.viewModel.switchCamera()
}).disposed(by: self.disposeBag)
self.buttonsContainer.switchSpeakerButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.viewModel.switchSpeaker()
}).disposed(by: self.disposeBag)
//Data bindings
self.viewModel.videoButtonState
.observeOn(MainScheduler.instance)
.bind(to: self.buttonsContainer.muteVideoButton.rx.image())
.disposed(by: self.disposeBag)
self.buttonsContainer.muteVideoButton.isEnabled = !(self.viewModel.call?.isAudioOnly ?? false)
self.viewModel.audioButtonState
.observeOn(MainScheduler.instance)
.bind(to: self.buttonsContainer.muteAudioButton.rx.image())
.disposed(by: self.disposeBag)
self.viewModel.speakerButtonState
.observeOn(MainScheduler.instance)
.bind(to: self.buttonsContainer.switchSpeakerButton.rx.image())
.disposed(by: self.disposeBag)
self.viewModel.speakerSwitchable
.observeOn(MainScheduler.instance)
.bind(to: self.buttonsContainer.switchSpeakerButton.rx.isEnabled)
.disposed(by: self.disposeBag)
self.buttonsContainer.switchSpeakerButton.isEnabled = !(self.viewModel.isHeadsetConnected)
self.viewModel.pauseCallButtonState
.observeOn(MainScheduler.instance)
.bind(to: self.buttonsContainer.pauseCallButton.rx.image())
.disposed(by: self.disposeBag)
// disable switch camera button for audio only calls
self.buttonsContainer.switchCameraButton.isEnabled = !(self.viewModel.isAudioOnly)
}
func setupBindings() {
self.viewModel.contactImageData.asObservable()
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] dataOrNil in
......@@ -168,40 +209,66 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
}
}).disposed(by: self.disposeBag)
self.viewModel.videoButtonState
self.viewModel.showCancelOption
.observeOn(MainScheduler.instance)
.bind(to: self.muteVideoButton.rx.image())
.disposed(by: self.disposeBag)
.subscribe(onNext: { show in
if show {
self.showCancelButton()
} else if !self.viewModel.isAudioOnly {
self.hideCancelButton()
}
}).disposed(by: self.disposeBag)
self.viewModel.videoMuted
.observeOn(MainScheduler.instance)
.bind(to: self.capturedVideo.rx.isHidden)
.disposed(by: self.disposeBag)
self.viewModel.audioButtonState
.observeOn(MainScheduler.instance)
.bind(to: self.muteAudioButton.rx.image())
.disposed(by: self.disposeBag)
self.viewModel.callButtonState
.observeOn(MainScheduler.instance)
.bind(to: self.pauseCallButton.rx.image())
.disposed(by: self.disposeBag)
self.audioOnlyImage.isHidden = !self.viewModel.isAudioOnly
self.viewModel.callPaused
.observeOn(MainScheduler.instance)
.bind(to: self.callView.rx.isHidden)
.disposed(by: self.disposeBag)
self.viewModel.callPaused
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [unowned self] show in
if show {
self.task?.cancel()
self.showCallOptions()
}
}).disposed(by: self.disposeBag)
}
func removeFromScreen() {
self.dismiss(animated: false)
}
@objc func screenTaped() {
@objc func screenTapped() {
let callState = self.viewModel.call?.state
if callState == .connecting || callState == .ringing {
return
}
self.viewModel.respondOnTap()
}
func showCancelButton() {
self.buttonsContainer.isHidden = false
self.buttonsContainer.bottomSpaceConstraint.constant = 90
self.view.layoutIfNeeded()
}
func hideCancelButton() {
self.buttonsContainer.isHidden = true
self.buttonsContainer.bottomSpaceConstraint.constant = 30
self.view.layoutIfNeeded()
}
func showCallOptions() {
self.buttonsContainer.isHidden = false
self.view.layoutIfNeeded()
}
func showContactInfo() {
if !self.infoContainer.isHidden {
task?.cancel()
......@@ -236,4 +303,10 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
self?.buttonsContainer.isHidden = true
})
}
func showAllInfo() {
self.buttonsContainer.isHidden = false
self.infoContainer.isHidden = false
self.infoLabelConstraint.constant = 0.00
}
}
......@@ -36,9 +36,14 @@ class CallViewModel: Stateable, ViewModel {
fileprivate let contactsService: ContactsService
fileprivate let accountService: AccountsService
fileprivate let videoService: VideoService
fileprivate let audioService: AudioService
private let disposeBag = DisposeBag()
fileprivate let log = SwiftyBeaver.self
var isHeadsetConnected = false
var isAudioOnly = false
var call: CallModel? {
didSet {
guard let call = self.call else {
......@@ -67,6 +72,9 @@ class CallViewModel: Stateable, ViewModel {
.disposed(by: self.disposeBag)
})