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 {
@IBOutlet weak var switchSpeakerButton: UIButton!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var switchCameraButton: UIButton!
@IBOutlet weak var acceptCallButton: UIButton!
//Constraints
@IBOutlet weak var cancelButtonWidthConstraint: NSLayoutConstraint!
@IBOutlet weak var cancelButtonCenterConstraint: NSLayoutConstraint!
@IBOutlet weak var cancelButtonBottomConstraint: NSLayoutConstraint!
@IBOutlet weak var cancelButtonHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var stackViewYConstraint: NSLayoutConstraint!
......@@ -87,19 +89,28 @@ class ButtonsContainerView: UIView, NibLoadable {
}
func withoutOptions() {
self.container.backgroundColor = UIColor.clear
self.backgroundBlurEffect.isHidden = true
switchCameraButton.isHidden = true
muteAudioButton.isHidden = true
muteVideoButton.isHidden = true
pauseCallButton.isHidden = true
dialpadButton.isHidden = true
switchSpeakerButton.isHidden = true
cancelButton.isHidden = false
cancelButtonBottomConstraint.constant = 20
self.container.backgroundColor = UIColor.clear
self.backgroundBlurEffect.isHidden = true
switchCameraButton.isHidden = true
muteAudioButton.isHidden = true
muteVideoButton.isHidden = true
pauseCallButton.isHidden = true
dialpadButton.isHidden = true
switchSpeakerButton.isHidden = true
cancelButton.isHidden = false
if self.viewModel?.isIncoming ?? false {
acceptCallButton.isHidden = false
cancelButtonBottomConstraint.constant = 60
cancelButtonCenterConstraint.constant = 55
return
}
cancelButtonCenterConstraint.constant = 0
cancelButtonBottomConstraint.constant = 20
}
func optionsWithSpeaker() {
acceptCallButton.isHidden = true
cancelButtonCenterConstraint.constant = 0
if !self.isCallStarted {
self.isCallStarted = true
self.backgroundBlurEffect.isHidden = false
......@@ -124,6 +135,8 @@ class ButtonsContainerView: UIView, NibLoadable {
}
func optionsWithoutSpeaker() {
acceptCallButton.isHidden = true
cancelButtonCenterConstraint.constant = 0
if !self.isCallStarted {
self.isCallStarted = true
if self.viewModel?.isAudioOnly ?? false {
......
......@@ -12,9 +12,11 @@
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ButtonsContainerView" customModule="Ring" customModuleProvider="target">
<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="cancelButton" destination="ZxT-mA-1xU" id="4nk-iI-vPV"/>
<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="cancelButtonWidthConstraint" destination="0vV-4C-odp" id="beL-yR-ehA"/>
<outlet property="container" destination="a9g-pf-bHy" id="6bw-CB-5qN"/>
......@@ -202,7 +204,7 @@
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="roundedCorners" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="1"/>
<real key="value" value="0.0"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
......@@ -212,12 +214,33 @@
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</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>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="w5l-pw-1ET" secondAttribute="bottom" id="6qu-Nn-b1Y"/>
<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 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 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"/>
......@@ -242,6 +265,7 @@
</objects>
<resources>
<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="disable_speakerphone" width="48" height="48"/>
<image name="pause_call" width="48" height="48"/>
......
......@@ -34,18 +34,20 @@ class ButtonsContainerViewModel {
let disposeBag = DisposeBag()
var isAudioOnly: Bool
var isSipCall: Bool
var isIncoming: Bool
let avalaibleCallOptions = BehaviorSubject<CallOptions>(value: .none)
lazy var observableCallOptions: Observable<CallOptions> = { [unowned self] in
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.audioService = audioService
self.callID = callID
self.isAudioOnly = isAudioOnly
self.isSipCall = isSipCall
self.isIncoming = isIncoming
checkCallOptions()
}
......
......@@ -182,6 +182,12 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
self?.removeFromScreen()
}).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
.subscribe(onNext: { [weak self] in
self?.viewModel.showDialpad()
......
......@@ -61,7 +61,8 @@ class CallViewModel: Stateable, ViewModel {
with: self.callService,
audioService: self.audioService,
callID: call.callId,
isSipCall: type)
isSipCall: type,
isIncoming: call.callType == .incoming)
}
}
......@@ -149,20 +150,23 @@ class CallViewModel: Stateable, ViewModel {
}()
lazy var bottomInfo: Observable<String> = {
return callService.currentCall
return callService
.currentCall
.filter({ [weak self] call in
return call.callId == self?.call?.callId &&
call.callType == .outgoing
}).map({ [weak self] call in
switch call.state {
case .connecting :
return L10n.Calls.connecting
return L10n.Calls.connecting
case .ringing :
return L10n.Calls.ringing
return L10n.Calls.ringing
case .over :
return L10n.Calls.callFinished
return L10n.Calls.callFinished
case .unknown :
return L10n.Calls.searching
default :
return ""
return ""
}
})
}()
......
......@@ -190,10 +190,14 @@ internal enum L10n {
internal static let callItemTitle = L10n.tr("Localizable", "calls.callItemTitle")
/// 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
internal static let incomingCallInfo = L10n.tr("Localizable", "calls.incomingCallInfo")
/// Ringing…
internal static let ringing = L10n.tr("Localizable", "calls.ringing")
/// Searching…
internal static let searching = L10n.tr("Localizable", "calls.searching")
/// Unknown
internal static let unknown = L10n.tr("Localizable", "calls.unknown")
}
......
......@@ -169,6 +169,20 @@ extension UIView {
gradient.endPoint = orientation.endPoint
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)
......
......@@ -139,7 +139,11 @@ class ContactRequestsViewModel: Stateable, ViewModel {
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
}
conversationViewModel.conversation = Variable<ConversationModel>(conversation)
......
......@@ -29,7 +29,7 @@ class MessageCellReceived: MessageCell {
override func applyBubbleStyleToCell(_ items: [MessageViewModel]?, cellForRowAt indexPath: IndexPath) {
super.applyBubbleStyleToCell(items, cellForRowAt: indexPath)
if (self.messageLabel.text?.containsOnlyEmoji ?? false) {
if self.messageLabel.text?.containsOnlyEmoji ?? false {
self.messageLabelTrailingConstraint.constant = 0
self.messageLabelLeadingConstraint.constant = 0
} else {
......
......@@ -59,18 +59,19 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa
self.showDialpad(inCall: inCall)
case .showGeneralSettings:
self.showGeneralSettings()
case .navigateToCall(let call):
self.openCall(call: call)
default:
break
}
}).disposed(by: self.disposeBag)
self.callService.newCall.asObservable()
.map({ call in
return call
}).observeOn(MainScheduler.instance)
self.callService.newCall
.asObservable()
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (call) in
self.showCallAlert(call: call)
}).disposed(by: self.disposeBag)
self.showCallController(call: call)
}).disposed(by: self.disposeBag)
self.navigationViewController.viewModel = ChatTabBarItemViewModel(with: self.injectionBag)
self.callbackPlaceCall()
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
}).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
.getAccount(fromAccountId: call.accountId) else {return}
if call.callId.isEmpty {
......@@ -169,35 +180,24 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa
helper.presentCallNotification(data: data, callService: self.callService)
return
}
var accountName = !account.registeredName.isEmpty ?
account.registeredName : account.type == AccountType.sip ?
account.username : account.jamiId
if let accountProfie = self.accountService.getAccountProfile(accountId: account.id), let name = accountProfie.alias,
!name.isEmpty {
accountName = name
let callViewController = CallViewController
.instantiate(with: self.injectionBag)
callViewController.viewModel.call = call
topController.present(callViewController, animated: true, completion: nil)
}
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
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)
self.showCallController(call: call)
}
}
......@@ -33,8 +33,8 @@
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dHy-gp-i6K" userLabel="Cellular Alert Label">
<rect key="frame" x="101" y="55" width="37.5" height="18"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dHy-gp-i6K" userLabel="Cellular Alert Label">
<rect key="frame" x="0.0" y="28" width="33" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
......@@ -72,6 +72,21 @@
<constraint firstItem="HKv-H1-GYI" firstAttribute="centerX" secondItem="e5o-cY-djH" secondAttribute="centerX" priority="750" id="yI5-9z-dUv"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Xqq-mY-gb7">
<rect key="frame" x="0.0" y="132" width="375" height="60"/>
<color key="backgroundColor" red="0.88391119240000005" green="0.82437592739999999" blue="0.76125866170000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="28"/>
<color key="textColor" red="0.1215686275" green="0.28627450980000002" blue="0.4431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yna-xz-mjc">
<rect key="frame" x="0.0" y="132" width="375" height="60"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="yBH-RL-bW2"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="18"/>
<color key="tintColor" red="0.1215686275" green="0.28627450980000002" blue="0.4431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DKd-eF-L6f">
<rect key="frame" x="0.0" y="20" width="375" height="56"/>
<subviews>
......@@ -123,14 +138,14 @@
</constraints>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="HFM-G6-hMN">
<rect key="frame" x="0.0" y="132" width="375" height="535"/>
<rect key="frame" x="0.0" y="192" width="375" height="475"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="EvL-Bu-O1T">
<rect key="frame" x="0.0" y="132" width="375" height="535"/>
<rect key="frame" x="0.0" y="192" width="375" height="475"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No conversations" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8bB-XU-6gh">
<rect key="frame" x="121" y="257" width="133" height="21"/>
<rect key="frame" x="121" y="227" width="133" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
......@@ -143,7 +158,7 @@
</constraints>
</view>
<tableView hidden="YES" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="1" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="opE-y7-3Rm">
<rect key="frame" x="0.0" y="132" width="375" height="535"/>
<rect key="frame" x="0.0" y="192" width="375" height="475"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<label key="tableHeaderView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4Yu-Fe-ixq">
<rect key="frame" x="0.0" y="0.0" width="375" height="24"/>
......@@ -198,24 +213,31 @@
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Xqq-mY-gb7" firstAttribute="leading" secondItem="yna-xz-mjc" secondAttribute="leading" id="0MV-oJ-y5E"/>
<constraint firstItem="opE-y7-3Rm" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="0cn-0y-g9L"/>
<constraint firstItem="EvL-Bu-O1T" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="2Cw-xT-xCg"/>
<constraint firstItem="yna-xz-mjc" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="6Vl-gz-mcw"/>
<constraint firstAttribute="trailing" secondItem="HFM-G6-hMN" secondAttribute="trailing" id="7H1-ka-2Ti"/>
<constraint firstItem="cfq-zl-uux" firstAttribute="top" secondItem="79Q-fh-vhV" secondAttribute="bottom" constant="30" id="8G6-0D-3ma"/>
<constraint firstItem="HFM-G6-hMN" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="8tr-Rd-1kr"/>
<constraint firstItem="DKd-eF-L6f" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="9fi-2r-wE8"/>
<constraint firstAttribute="trailing" secondItem="DKd-eF-L6f" secondAttribute="trailing" id="AhH-s9-pfe"/>
<constraint firstAttribute="trailing" secondItem="opE-y7-3Rm" secondAttribute="trailing" id="D0f-nM-KEs"/>
<constraint firstItem="HFM-G6-hMN" firstAttribute="top" secondItem="e5o-cY-djH" secondAttribute="bottom" id="KK7-rp-AZz"/>
<constraint firstItem="opE-y7-3Rm" firstAttribute="top" secondItem="e5o-cY-djH" secondAttribute="bottom" id="NKz-A0-hLf"/>
<constraint firstItem="yna-xz-mjc" firstAttribute="top" secondItem="e5o-cY-djH" secondAttribute="bottom" id="DCG-3a-0Xn"/>
<constraint firstItem="cfq-zl-uux" firstAttribute="top" secondItem="EvL-Bu-O1T" secondAttribute="bottom" id="TsM-9H-eI1"/>
<constraint firstItem="EvL-Bu-O1T" firstAttribute="top" secondItem="yna-xz-mjc" secondAttribute="bottom" id="Ub4-a0-nfv"/>
<constraint firstItem="Xqq-mY-gb7" firstAttribute="trailing" secondItem="yna-xz-mjc" secondAttribute="trailing" id="Uqt-Q7-YKn"/>
<constraint firstItem="cfq-zl-uux" firstAttribute="top" secondItem="HFM-G6-hMN" secondAttribute="bottom" id="VfB-5H-uHq"/>
<constraint firstItem="EvL-Bu-O1T" firstAttribute="top" secondItem="e5o-cY-djH" secondAttribute="bottom" id="aH0-7e-i4a"/>
<constraint firstItem="HFM-G6-hMN" firstAttribute="top" secondItem="yna-xz-mjc" secondAttribute="bottom" id="ecE-o9-3qW"/>
<constraint firstAttribute="trailing" secondItem="79Q-fh-vhV" secondAttribute="trailing" constant="30" id="elh-uT-3vG"/>
<constraint firstAttribute="trailing" secondItem="yna-xz-mjc" secondAttribute="trailing" id="fnJ-s3-jmB"/>
<constraint firstItem="DKd-eF-L6f" firstAttribute="top" secondItem="sbJ-yn-t3e" secondAttribute="bottom" id="gbl-PR-kL4"/>
<constraint firstItem="opE-y7-3Rm" firstAttribute="top" secondItem="yna-xz-mjc" secondAttribute="bottom" id="hef-Ug-iKt"/>
<constraint firstItem="Xqq-mY-gb7" firstAttribute="top" secondItem="yna-xz-mjc" secondAttribute="top" id="k9U-bW-Djc"/>
<constraint firstAttribute="trailing" secondItem="e5o-cY-djH" secondAttribute="trailing" id="k9v-18-oxL"/>
<constraint firstItem="e5o-cY-djH" firstAttribute="top" secondItem="xPr-nI-I35" secondAttribute="bottom" id="mKv-lK-yDx"/>
<constraint firstAttribute="trailing" secondItem="EvL-Bu-O1T" secondAttribute="trailing" id="nSK-QH-snj"/>
<constraint firstItem="Xqq-mY-gb7" firstAttribute="bottom" secondItem="yna-xz-mjc" secondAttribute="bottom" id="qfL-UU-vtC"/>
<constraint firstItem="e5o-cY-djH" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="rXu-fF-ESz"/>
<constraint firstItem="cfq-zl-uux" firstAttribute="top" secondItem="opE-y7-3Rm" secondAttribute="bottom" id="wun-hC-1JP"/>
</constraints>
......@@ -223,8 +245,11 @@
<extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
<navigationItem key="navigationItem" id="zLl-0A-Dht"/>
<connections>
<outlet property="callButtonHeightConstraint" destination="yBH-RL-bW2" id="s9Y-L7-0en"/>
<outlet property="cellularAlertLabel" destination="dHy-gp-i6K" id="W9w-Mi-GTY"/>
<outlet property="conversationsTableView" destination="HFM-G6-hMN" id="M97-IB-NUZ"/>
<outlet property="currentCallButton" destination="yna-xz-mjc" id="gUe-Hu-LxI"/>
<outlet property="currentCallLabel" destination="Xqq-mY-gb7" id="8Ty-Qf-5Py"/>
<outlet property="dialpadButton" destination="k8G-Me-4BI" id="Ij7-SF-nvZ"/>
<outlet property="dialpadButtonShadow" destination="79Q-fh-vhV" id="VcA-wc-j6h"/>
<outlet property="networkAlertLabel" destination="Fu7-Dr-XvA" id="0qV-lk-9mE"/>
......
......@@ -27,6 +27,7 @@ import RxCocoa
import Reusable
import SwiftyBeaver
import ContactsUI
import QuartzCore
//Constants
private struct SmartlistConstants {
......@@ -37,6 +38,7 @@ private struct SmartlistConstants {
}
// swiftlint:disable type_body_length
// swiftlint:disable file_length
class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased {
private let log = SwiftyBeaver.self
......@@ -58,7 +60,10 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
@IBOutlet weak var searchBarShadow: UIView!
@IBOutlet weak var qrScanButton: UIButton!
@IBOutlet weak var phoneBookButton: UIButton!
@IBOutlet weak var currentCallButton: UIButton!
@IBOutlet weak var currentCallLabel: UILabel!
@IBOutlet weak var scanButtonLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var callButtonHeightConstraint: NSLayoutConstraint!
// account selection
var accounPicker = UIPickerView()
......@@ -107,6 +112,9 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !currentCallLabel.isHidden {
self.currentCallLabel.blink()
}
self.navigationController?.navigationBar.layer.shadowColor = UIColor.clear.cgColor
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationController?.navigationBar
......@@ -245,6 +253,36 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
.disposed(by: self.disposeBag)
self.conversationsTableView.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() {
......
......@@ -23,6 +23,7 @@
import RxSwift
import SwiftyBeaver
// swiftlint:disable type_body_length
class SmartlistViewModel: Stateable, ViewModel {
private let log = SwiftyBeaver.self
......@@ -43,6 +44,7 @@ class SmartlistViewModel: Stateable, ViewModel {
fileprivate let contactsService: ContactsService
fileprivate let networkService: NetworkService
fileprivate let profileService: ProfilesService
fileprivate let callService: CallsService
let searchBarText = Variable<String>("")
var isSearching: Observable<Bool>!
......@@ -77,6 +79,7 @@ class SmartlistViewModel: Stateable, ViewModel {
return false
}).observeOn(MainScheduler.instance)
}()
var searchStatus = PublishSubject<String>()
var connectionState = PublishSubject<ConnectionType>()
lazy var accounts: Observable<[AccountItem]> = { [unowned self] in
......@@ -205,6 +208,7 @@ class SmartlistViewModel: Stateable, ViewModel {
self.contactsService = injectionBag.contactsService
self.networkService = injectionBag.networkService
self.profileService = injectionBag.profileService
self.callService = injectionBag.callService
self.injectionBag = injectionBag
self.accountsService.currentAccountChanged
......@@ -409,4 +413,50 @@ class SmartlistViewModel: Stateable, ViewModel {
func 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
}
self.stateSubject.onNext(ConversationState.navigateToCall(call: call))
}
}
......@@ -24,6 +24,7 @@ import Foundation
import RxSwift
import RxDataSources
// swiftlint:disable file_length
enum SettingsSection: SectionModelType {
typealias Item = SectionRow
......
......@@ -54,7 +54,7 @@ enum CallDetailKey: String {
case audioOnlyKey = "AUDIO_ONLY"
}
class CallModel {
public class CallModel {
var callId: String = ""
var dateReceived: Date?
......
......@@ -30,6 +30,7 @@ enum ConversationState: State {
case createNewAccount()
case showDialpad(inCall: Bool)
case showGeneralSettings()
case navigateToCall(call: CallModel)
}
protocol ConversationNavigation: class {
......
......@@ -136,7 +136,9 @@
"calls.incomingCallInfo" = "wants to talk to you";
"calls.ringing" = "Ringing…";
"calls.connecting" = "Connecting…";
"calls.searching" = "Searching…";
"calls.callFinished" = "Call finished";
"calls.currentCallWith" = "Call with ";
//Account Page
"accountPage.devicesListHeader" = "Devices";
......
......@@ -173,7 +173,7 @@ class CallsService: CallsAdapterDelegate {
callDetails[CallDetailKey.audioOnlyKey.rawValue] = isAudioOnly.toString()
callDetails[CallDetailKey.timeStampStartKey.rawValue] = ""
let call = CallModel(withCallId: ringId, callDetails: callDetails)
call.state = .connecting
call.state = .unknown
call.callType = .outgoing