Commit 5f20a72b authored by Kateryna Kostiuk's avatar Kateryna Kostiuk

UI/UX: call screen

- display call screen instead of alert for incoming call
- add option to navigate to call screen from smart list
- add searching status

Change-Id: I87ce18275038d8142f2c2826b976bb81648b74c3
parent 53d1158d
...@@ -36,9 +36,11 @@ class ButtonsContainerView: UIView, NibLoadable { ...@@ -36,9 +36,11 @@ class ButtonsContainerView: UIView, NibLoadable {
@IBOutlet weak var switchSpeakerButton: UIButton! @IBOutlet weak var switchSpeakerButton: UIButton!
@IBOutlet weak var cancelButton: UIButton! @IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var switchCameraButton: UIButton! @IBOutlet weak var switchCameraButton: UIButton!
@IBOutlet weak var acceptCallButton: UIButton!
//Constraints //Constraints
@IBOutlet weak var cancelButtonWidthConstraint: NSLayoutConstraint! @IBOutlet weak var cancelButtonWidthConstraint: NSLayoutConstraint!
@IBOutlet weak var cancelButtonCenterConstraint: NSLayoutConstraint!
@IBOutlet weak var cancelButtonBottomConstraint: NSLayoutConstraint! @IBOutlet weak var cancelButtonBottomConstraint: NSLayoutConstraint!
@IBOutlet weak var cancelButtonHeightConstraint: NSLayoutConstraint! @IBOutlet weak var cancelButtonHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var stackViewYConstraint: NSLayoutConstraint! @IBOutlet weak var stackViewYConstraint: NSLayoutConstraint!
...@@ -87,19 +89,28 @@ class ButtonsContainerView: UIView, NibLoadable { ...@@ -87,19 +89,28 @@ class ButtonsContainerView: UIView, NibLoadable {
} }
func withoutOptions() { func withoutOptions() {
self.container.backgroundColor = UIColor.clear self.container.backgroundColor = UIColor.clear
self.backgroundBlurEffect.isHidden = true self.backgroundBlurEffect.isHidden = true
switchCameraButton.isHidden = true switchCameraButton.isHidden = true
muteAudioButton.isHidden = true muteAudioButton.isHidden = true
muteVideoButton.isHidden = true muteVideoButton.isHidden = true
pauseCallButton.isHidden = true pauseCallButton.isHidden = true
dialpadButton.isHidden = true dialpadButton.isHidden = true
switchSpeakerButton.isHidden = true switchSpeakerButton.isHidden = true
cancelButton.isHidden = false cancelButton.isHidden = false
cancelButtonBottomConstraint.constant = 20 if self.viewModel?.isIncoming ?? false {
acceptCallButton.isHidden = false
cancelButtonBottomConstraint.constant = 60
cancelButtonCenterConstraint.constant = 55
return
}
cancelButtonCenterConstraint.constant = 0
cancelButtonBottomConstraint.constant = 20
} }
func optionsWithSpeaker() { func optionsWithSpeaker() {
acceptCallButton.isHidden = true
cancelButtonCenterConstraint.constant = 0
if !self.isCallStarted { if !self.isCallStarted {
self.isCallStarted = true self.isCallStarted = true
self.backgroundBlurEffect.isHidden = false self.backgroundBlurEffect.isHidden = false
...@@ -124,6 +135,8 @@ class ButtonsContainerView: UIView, NibLoadable { ...@@ -124,6 +135,8 @@ class ButtonsContainerView: UIView, NibLoadable {
} }
func optionsWithoutSpeaker() { func optionsWithoutSpeaker() {
acceptCallButton.isHidden = true
cancelButtonCenterConstraint.constant = 0
if !self.isCallStarted { if !self.isCallStarted {
self.isCallStarted = true self.isCallStarted = true
if self.viewModel?.isAudioOnly ?? false { if self.viewModel?.isAudioOnly ?? false {
......
...@@ -12,9 +12,11 @@ ...@@ -12,9 +12,11 @@
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ButtonsContainerView" customModule="Ring" customModuleProvider="target"> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ButtonsContainerView" customModule="Ring" customModuleProvider="target">
<connections> <connections>
<outlet property="acceptCallButton" destination="rfz-9h-HoH" id="V6j-8E-g68"/>
<outlet property="backgroundBlurEffect" destination="w5l-pw-1ET" id="YYh-qB-WIL"/> <outlet property="backgroundBlurEffect" destination="w5l-pw-1ET" id="YYh-qB-WIL"/>
<outlet property="cancelButton" destination="ZxT-mA-1xU" id="4nk-iI-vPV"/> <outlet property="cancelButton" destination="ZxT-mA-1xU" id="4nk-iI-vPV"/>
<outlet property="cancelButtonBottomConstraint" destination="Ilu-Zu-JqW" id="Yeg-Ca-8pf"/> <outlet property="cancelButtonBottomConstraint" destination="Ilu-Zu-JqW" id="Yeg-Ca-8pf"/>
<outlet property="cancelButtonCenterConstraint" destination="ls1-Ze-LXF" id="QlC-QB-oPz"/>
<outlet property="cancelButtonHeightConstraint" destination="kls-aA-2zS" id="CzE-vC-V5Z"/> <outlet property="cancelButtonHeightConstraint" destination="kls-aA-2zS" id="CzE-vC-V5Z"/>
<outlet property="cancelButtonWidthConstraint" destination="0vV-4C-odp" id="beL-yR-ehA"/> <outlet property="cancelButtonWidthConstraint" destination="0vV-4C-odp" id="beL-yR-ehA"/>
<outlet property="container" destination="a9g-pf-bHy" id="6bw-CB-5qN"/> <outlet property="container" destination="a9g-pf-bHy" id="6bw-CB-5qN"/>
...@@ -202,7 +204,7 @@ ...@@ -202,7 +204,7 @@
<userDefinedRuntimeAttributes> <userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="roundedCorners" value="YES"/> <userDefinedRuntimeAttribute type="boolean" keyPath="roundedCorners" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth"> <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="1"/> <real key="value" value="0.0"/>
</userDefinedRuntimeAttribute> </userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor"> <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> <color key="value" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
...@@ -212,12 +214,33 @@ ...@@ -212,12 +214,33 @@
</userDefinedRuntimeAttribute> </userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</button> </button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rfz-9h-HoH" userLabel="Accept">
<rect key="frame" x="42.5" y="10" width="70" height="70"/>
<color key="backgroundColor" red="0.45098039215686275" green="0.98039215686274506" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="70" id="cRi-F4-pPw"/>
<constraint firstAttribute="height" constant="70" id="vhg-1N-VIL"/>
</constraints>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" image="call_button"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="roundedCorners" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="0.0"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
<real key="value" value="35"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</button>
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstAttribute="bottom" secondItem="w5l-pw-1ET" secondAttribute="bottom" id="6qu-Nn-b1Y"/> <constraint firstAttribute="bottom" secondItem="w5l-pw-1ET" secondAttribute="bottom" id="6qu-Nn-b1Y"/>
<constraint firstAttribute="height" constant="200" id="Gjk-7U-rEe"/> <constraint firstAttribute="height" constant="200" id="Gjk-7U-rEe"/>
<constraint firstItem="ZxT-mA-1xU" firstAttribute="leading" secondItem="rfz-9h-HoH" secondAttribute="trailing" constant="40" id="Hwg-PM-KNI"/>
<constraint firstAttribute="bottom" secondItem="ZxT-mA-1xU" secondAttribute="bottom" constant="120" id="Ilu-Zu-JqW"/> <constraint firstAttribute="bottom" secondItem="ZxT-mA-1xU" secondAttribute="bottom" constant="120" id="Ilu-Zu-JqW"/>
<constraint firstItem="rfz-9h-HoH" firstAttribute="centerY" secondItem="ZxT-mA-1xU" secondAttribute="centerY" id="NZg-SL-A31"/>
<constraint firstAttribute="trailing" secondItem="w5l-pw-1ET" secondAttribute="trailing" id="TnQ-lp-9B9"/> <constraint firstAttribute="trailing" secondItem="w5l-pw-1ET" secondAttribute="trailing" id="TnQ-lp-9B9"/>
<constraint firstItem="RHx-cL-CV5" firstAttribute="leading" secondItem="a9g-pf-bHy" secondAttribute="leading" constant="15" id="Y9c-4n-eid"/> <constraint firstItem="RHx-cL-CV5" firstAttribute="leading" secondItem="a9g-pf-bHy" secondAttribute="leading" constant="15" id="Y9c-4n-eid"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="450" id="ZfR-cx-MUB"/> <constraint firstAttribute="width" relation="lessThanOrEqual" constant="450" id="ZfR-cx-MUB"/>
...@@ -242,6 +265,7 @@ ...@@ -242,6 +265,7 @@
</objects> </objects>
<resources> <resources>
<image name="audio_running" width="48" height="48"/> <image name="audio_running" width="48" height="48"/>
<image name="call_button" width="43.5" height="43.5"/>
<image name="dialpad" width="75" height="75"/> <image name="dialpad" width="75" height="75"/>
<image name="disable_speakerphone" width="48" height="48"/> <image name="disable_speakerphone" width="48" height="48"/>
<image name="pause_call" width="48" height="48"/> <image name="pause_call" width="48" height="48"/>
......
...@@ -34,18 +34,20 @@ class ButtonsContainerViewModel { ...@@ -34,18 +34,20 @@ class ButtonsContainerViewModel {
let disposeBag = DisposeBag() let disposeBag = DisposeBag()
var isAudioOnly: Bool var isAudioOnly: Bool
var isSipCall: Bool var isSipCall: Bool
var isIncoming: Bool
let avalaibleCallOptions = BehaviorSubject<CallOptions>(value: .none) let avalaibleCallOptions = BehaviorSubject<CallOptions>(value: .none)
lazy var observableCallOptions: Observable<CallOptions> = { [unowned self] in lazy var observableCallOptions: Observable<CallOptions> = { [unowned self] in
return self.avalaibleCallOptions.asObservable() return self.avalaibleCallOptions.asObservable()
}() }()
init(isAudioOnly: Bool, with callService: CallsService, audioService: AudioService, callID: String, isSipCall: Bool) { init(isAudioOnly: Bool, with callService: CallsService, audioService: AudioService, callID: String, isSipCall: Bool, isIncoming: Bool) {
self.callService = callService self.callService = callService
self.audioService = audioService self.audioService = audioService
self.callID = callID self.callID = callID
self.isAudioOnly = isAudioOnly self.isAudioOnly = isAudioOnly
self.isSipCall = isSipCall self.isSipCall = isSipCall
self.isIncoming = isIncoming
checkCallOptions() checkCallOptions()
} }
......
...@@ -182,6 +182,12 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { ...@@ -182,6 +182,12 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
self?.removeFromScreen() self?.removeFromScreen()
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
self.buttonsContainer.acceptCallButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.viewModel.answerCall()
.subscribe()
}).disposed(by: self.disposeBag)
self.buttonsContainer.dialpadButton.rx.tap self.buttonsContainer.dialpadButton.rx.tap
.subscribe(onNext: { [weak self] in .subscribe(onNext: { [weak self] in
self?.viewModel.showDialpad() self?.viewModel.showDialpad()
......
...@@ -61,7 +61,8 @@ class CallViewModel: Stateable, ViewModel { ...@@ -61,7 +61,8 @@ class CallViewModel: Stateable, ViewModel {
with: self.callService, with: self.callService,
audioService: self.audioService, audioService: self.audioService,
callID: call.callId, callID: call.callId,
isSipCall: type) isSipCall: type,
isIncoming: call.callType == .incoming)
} }
} }
...@@ -149,20 +150,23 @@ class CallViewModel: Stateable, ViewModel { ...@@ -149,20 +150,23 @@ class CallViewModel: Stateable, ViewModel {
}() }()
lazy var bottomInfo: Observable<String> = { lazy var bottomInfo: Observable<String> = {
return callService.currentCall return callService
.currentCall
.filter({ [weak self] call in .filter({ [weak self] call in
return call.callId == self?.call?.callId && return call.callId == self?.call?.callId &&
call.callType == .outgoing call.callType == .outgoing
}).map({ [weak self] call in }).map({ [weak self] call in
switch call.state { switch call.state {
case .connecting : case .connecting :
return L10n.Calls.connecting return L10n.Calls.connecting
case .ringing : case .ringing :
return L10n.Calls.ringing return L10n.Calls.ringing
case .over : case .over :
return L10n.Calls.callFinished return L10n.Calls.callFinished
case .unknown :
return L10n.Calls.searching
default : default :
return "" return ""
} }
}) })
}() }()
......
...@@ -190,10 +190,14 @@ internal enum L10n { ...@@ -190,10 +190,14 @@ internal enum L10n {
internal static let callItemTitle = L10n.tr("Localizable", "calls.callItemTitle") internal static let callItemTitle = L10n.tr("Localizable", "calls.callItemTitle")
/// Connecting… /// Connecting…
internal static let connecting = L10n.tr("Localizable", "calls.connecting") internal static let connecting = L10n.tr("Localizable", "calls.connecting")
/// Call with
internal static let currentCallWith = L10n.tr("Localizable", "calls.currentCallWith")
/// wants to talk to you /// wants to talk to you
internal static let incomingCallInfo = L10n.tr("Localizable", "calls.incomingCallInfo") internal static let incomingCallInfo = L10n.tr("Localizable", "calls.incomingCallInfo")
/// Ringing… /// Ringing…
internal static let ringing = L10n.tr("Localizable", "calls.ringing") internal static let ringing = L10n.tr("Localizable", "calls.ringing")
/// Searching…
internal static let searching = L10n.tr("Localizable", "calls.searching")
/// Unknown /// Unknown
internal static let unknown = L10n.tr("Localizable", "calls.unknown") internal static let unknown = L10n.tr("Localizable", "calls.unknown")
} }
......
...@@ -169,6 +169,20 @@ extension UIView { ...@@ -169,6 +169,20 @@ extension UIView {
gradient.endPoint = orientation.endPoint gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0) self.layer.insertSublayer(gradient, at: 0)
} }
func blink() {
UIView.animate(withDuration: 1,
delay: 0.0,
options: [.curveEaseInOut,
.autoreverse,
.repeat],
animations: { [weak self] in
self?.alpha = 0.4
},
completion: { [weak self] _ in
self?.alpha = 1.0
})
}
} }
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint) typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
......
...@@ -139,7 +139,11 @@ class ContactRequestsViewModel: Stateable, ViewModel { ...@@ -139,7 +139,11 @@ class ContactRequestsViewModel: Stateable, ViewModel {
return return
} }
guard let conversation = self.conversationService.findConversation(withRingId: ringId, withAccountId: account.id) else { guard let uri = JamiURI(schema: URIType.ring, infoHach: ringId).uriString else {
return
}
guard let conversation = self.conversationService.findConversation(withRingId: uri, withAccountId: account.id) else {
return return
} }
conversationViewModel.conversation = Variable<ConversationModel>(conversation) conversationViewModel.conversation = Variable<ConversationModel>(conversation)
......
...@@ -29,7 +29,7 @@ class MessageCellReceived: MessageCell { ...@@ -29,7 +29,7 @@ class MessageCellReceived: MessageCell {
override func applyBubbleStyleToCell(_ items: [MessageViewModel]?, cellForRowAt indexPath: IndexPath) { override func applyBubbleStyleToCell(_ items: [MessageViewModel]?, cellForRowAt indexPath: IndexPath) {
super.applyBubbleStyleToCell(items, cellForRowAt: indexPath) super.applyBubbleStyleToCell(items, cellForRowAt: indexPath)
if (self.messageLabel.text?.containsOnlyEmoji ?? false) { if self.messageLabel.text?.containsOnlyEmoji ?? false {
self.messageLabelTrailingConstraint.constant = 0 self.messageLabelTrailingConstraint.constant = 0
self.messageLabelLeadingConstraint.constant = 0 self.messageLabelLeadingConstraint.constant = 0
} else { } else {
......
...@@ -59,18 +59,19 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa ...@@ -59,18 +59,19 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa
self.showDialpad(inCall: inCall) self.showDialpad(inCall: inCall)
case .showGeneralSettings: case .showGeneralSettings:
self.showGeneralSettings() self.showGeneralSettings()
case .navigateToCall(let call):
self.openCall(call: call)
default: default:
break break
} }
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
self.callService.newCall.asObservable() self.callService.newCall
.map({ call in .asObservable()
return call .observeOn(MainScheduler.instance)
}).observeOn(MainScheduler.instance)
.subscribe(onNext: { (call) in .subscribe(onNext: { (call) in
self.showCallAlert(call: call) self.showCallController(call: call)
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
self.navigationViewController.viewModel = ChatTabBarItemViewModel(with: self.injectionBag) self.navigationViewController.viewModel = ChatTabBarItemViewModel(with: self.injectionBag)
self.callbackPlaceCall() self.callbackPlaceCall()
NotificationCenter.default.addObserver(self, selector: #selector(self.incomingCall(_:)), name: NSNotification.Name(NotificationName.answerCallFromNotifications.rawValue), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.incomingCall(_:)), name: NSNotification.Name(NotificationName.answerCallFromNotifications.rawValue), object: nil)
...@@ -150,7 +151,17 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa ...@@ -150,7 +151,17 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
} }
private func showCallAlert(call: CallModel) { func showCallController (call: CallModel) {
guard var topController = UIApplication.shared
.keyWindow?.rootViewController else {
return
}
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
if topController.isKind(of: (CallViewController).self) {
return
}
guard let account = self.accountService guard let account = self.accountService
.getAccount(fromAccountId: call.accountId) else {return} .getAccount(fromAccountId: call.accountId) else {return}
if call.callId.isEmpty { if call.callId.isEmpty {
...@@ -169,35 +180,24 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa ...@@ -169,35 +180,24 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa
helper.presentCallNotification(data: data, callService: self.callService) helper.presentCallNotification(data: data, callService: self.callService)
return return
} }
var accountName = !account.registeredName.isEmpty ? let callViewController = CallViewController
account.registeredName : account.type == AccountType.sip ? .instantiate(with: self.injectionBag)
account.username : account.jamiId callViewController.viewModel.call = call
if let accountProfie = self.accountService.getAccountProfile(accountId: account.id), let name = accountProfie.alias, topController.present(callViewController, animated: true, completion: nil)
!name.isEmpty { }
accountName = name
func openCall (call: CallModel) {
let controlles = self.navigationViewController.viewControllers
for controller in controlles
where controller.isKind(of: (CallViewController).self) {
if let callcontroller = controller as? CallViewController, callcontroller.viewModel.call?.callId == call.callId {
self.navigationViewController
.present(callcontroller,
animated: true,
completion: nil)
return
}
} }
let message = accountName.isEmpty ? nil : "To: " + accountName self.showCallController(call: call)
let alertStyle = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad) ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet
let alert = UIAlertController(title: L10n.Alerts.incomingCallAllertTitle + "\(call.displayName)", message: message, preferredStyle: alertStyle)
alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonAccept, style: UIAlertActionStyle.default, handler: { (_) in
self.answerIncomingCall(call: call)
alert.dismiss(animated: true, completion: nil)}))
alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonIgnore, style: UIAlertActionStyle.default, handler: { (_) in
self.injectionBag.callService.refuse(callId: call.callId)
.subscribe({_ in
print("Call ignored")
}).disposed(by: self.disposeBag)
alert.dismiss(animated: true, completion: nil)
}))
self.present(viewController: alert, withStyle: .present, withAnimation: true, disposeBag: self.disposeBag)
self.callService.currentCall.takeUntil(alert.rx.controllerWasDismissed).filter({ currentCall in
return currentCall.callId == call.callId &&
(currentCall.state == .over || currentCall.state == .failure)
}).subscribe(onNext: { _ in
DispatchQueue.main.async {
alert.dismiss(animated: true, completion: nil)
}
}).disposed(by: self.disposeBag)
} }
} }
...@@ -27,6 +27,7 @@ import RxCocoa ...@@ -27,6 +27,7 @@ import RxCocoa
import Reusable import Reusable
import SwiftyBeaver import SwiftyBeaver
import ContactsUI import ContactsUI
import QuartzCore
//Constants //Constants
private struct SmartlistConstants { private struct SmartlistConstants {
...@@ -37,6 +38,7 @@ private struct SmartlistConstants { ...@@ -37,6 +38,7 @@ private struct SmartlistConstants {
} }
// swiftlint:disable type_body_length // swiftlint:disable type_body_length
// swiftlint:disable file_length
class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased { class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased {
private let log = SwiftyBeaver.self private let log = SwiftyBeaver.self
...@@ -58,7 +60,10 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased ...@@ -58,7 +60,10 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
@IBOutlet weak var searchBarShadow: UIView! @IBOutlet weak var searchBarShadow: UIView!
@IBOutlet weak var qrScanButton: UIButton! @IBOutlet weak var qrScanButton: UIButton!
@IBOutlet weak var phoneBookButton: UIButton! @IBOutlet weak var phoneBookButton: UIButton!
@IBOutlet weak var currentCallButton: UIButton!
@IBOutlet weak var currentCallLabel: UILabel!
@IBOutlet weak var scanButtonLeadingConstraint: NSLayoutConstraint! @IBOutlet weak var scanButtonLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var callButtonHeightConstraint: NSLayoutConstraint!
// account selection // account selection
var accounPicker = UIPickerView() var accounPicker = UIPickerView()
...@@ -107,6 +112,9 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased ...@@ -107,6 +112,9 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
if !currentCallLabel.isHidden {
self.currentCallLabel.blink()
}
self.navigationController?.navigationBar.layer.shadowColor = UIColor.clear.cgColor self.navigationController?.navigationBar.layer.shadowColor = UIColor.clear.cgColor
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationController?.navigationBar self.navigationController?.navigationBar
...@@ -245,6 +253,36 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased ...@@ -245,6 +253,36 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
.disposed(by: self.disposeBag) .disposed(by: self.disposeBag)
self.conversationsTableView.tableFooterView = UIView() self.conversationsTableView.tableFooterView = UIView()
self.searchResultsTableView.tableFooterView = UIView() self.searchResultsTableView.tableFooterView = UIView()
self.currentCallButton.isHidden = true
self.currentCallLabel.isHidden = true
self.callButtonHeightConstraint.constant = 0
self.viewModel.showCallButton
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [unowned self] show in
if show {
self.currentCallButton.isHidden = false
self.currentCallLabel.isHidden = false
self.currentCallLabel.blink()
self.callButtonHeightConstraint.constant = 60
return
}
self.currentCallButton.isHidden = true
self.currentCallLabel.isHidden = true
self.callButtonHeightConstraint.constant = 0
self.currentCallLabel.layer.removeAllAnimations()
}).disposed(by: disposeBag)
self.viewModel.callButtonTitle
.observeOn(MainScheduler.instance)
.bind(to: self.currentCallButton.rx.title(for: .normal))
.disposed(by: disposeBag)
currentCallButton.rx.tap
.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] in
self.viewModel.openCall()
})
.disposed(by: self.disposeBag)
} }
func confugureAccountPicker() { func confugureAccountPicker() {
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
import RxSwift import RxSwift
import SwiftyBeaver import SwiftyBeaver
// swiftlint:disable type_body_length
class SmartlistViewModel: Stateable, ViewModel { class SmartlistViewModel: Stateable, ViewModel {
private let log = SwiftyBeaver.self private let log = SwiftyBeaver.self
...@@ -43,6 +44,7 @@ class SmartlistViewModel: Stateable, ViewModel { ...@@ -43,6 +44,7 @@ class SmartlistViewModel: Stateable, ViewModel {
fileprivate let contactsService: ContactsService fileprivate let contactsService: ContactsService
fileprivate let networkService: NetworkService fileprivate let networkService: NetworkService
fileprivate let profileService: ProfilesService fileprivate let profileService: ProfilesService
fileprivate let callService: CallsService
let searchBarText = Variable<String>("") let searchBarText = Variable<String>("")
var isSearching: Observable<Bool>! var isSearching: Observable<Bool>!
...@@ -77,6 +79,7 @@ class SmartlistViewModel: Stateable, ViewModel { ...@@ -77,6 +79,7 @@ class SmartlistViewModel: Stateable, ViewModel {
return false return false
}).observeOn(MainScheduler.instance) }).observeOn(MainScheduler.instance)
}() }()
var searchStatus = PublishSubject<String>() var searchStatus = PublishSubject<String>()
var connectionState = PublishSubject<ConnectionType>() var connectionState = PublishSubject<ConnectionType>()
lazy var accounts: Observable<[AccountItem]> = { [unowned self] in lazy var accounts: Observable<[AccountItem]> = { [unowned self] in
...@@ -205,6 +208,7 @@ class SmartlistViewModel: Stateable, ViewModel { ...@@ -205,6 +208,7 @@ class SmartlistViewModel: Stateable, ViewModel {
self.contactsService = injectionBag.contactsService self.contactsService = injectionBag.contactsService
self.networkService = injectionBag.networkService self.networkService = injectionBag.networkService
self.profileService = injectionBag.profileService self.profileService = injectionBag.profileService
self.callService = injectionBag.callService
self.injectionBag = injectionBag self.injectionBag = injectionBag
self.accountsService.currentAccountChanged self.accountsService.currentAccountChanged
...@@ -409,4 +413,50 @@ class SmartlistViewModel: Stateable, ViewModel { ...@@ -409,4 +413,50 @@ class SmartlistViewModel: Stateable, ViewModel {
func showGeneralSettings() { func showGeneralSettings() {
self.stateSubject.onNext(ConversationState.showGeneralSettings()) self.stateSubject.onNext(ConversationState.showGeneralSettings())
} }
lazy var callButtonTitle: Observable<String> = { [unowned self] in
return self.callService
.currentCall
.share()
.asObservable()
.map({ call in
let callIsValid = self.callIsValid(call: call)
let title = callIsValid ?
call.stateValue == CallState.incoming.rawValue ?
L10n.Alerts.incomingCallAllertTitle + "\(call.displayName)" :
L10n.Calls.currentCallWith + "\(call.displayName)" : ""
return title
})
}()
lazy var showCallButton: Observable<Bool> = { [unowned self] in
return self.callService
.currentCall
.share()
.asObservable()
.map({ call in
let callIsValid = self.callIsValid(call: call)
self.currentCallId.value = callIsValid ? call.callId : ""
return callIsValid
})
}()
let currentCallId = Variable<String>("")
func callIsValid (call: CallModel) -> Bool {
return call.stateValue == CallState.hold.rawValue ||
call.stateValue == CallState.unhold.rawValue ||
call.stateValue == CallState.incoming.rawValue ||
call.stateValue == CallState.connecting.rawValue ||
call.stateValue == CallState.ringing.rawValue ||
call.stateValue == CallState.current.rawValue
}
func openCall() {
guard let call = self.callService
.call(callID: self.currentCallId.value) else {
return
}