Commit 2ebdc425 authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Andreas Traczyk

call: add call screen

Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
Change-Id: I7dfcc20e864967c7d34964c9f7699b5495146ea8
Reviewed-by: Andreas Traczyk's avatarAndreas Traczyk <andreas.traczyk@savoirfairelinux.com>
parent c6c0df8b
......@@ -110,6 +110,9 @@
0E49096C1FEAB225005CAA50 /* CallsAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49096B1FEAB225005CAA50 /* CallsAdapterDelegate.swift */; };
0E49096E1FEAC0DE005CAA50 /* CallsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49096D1FEAC0DE005CAA50 /* CallsService.swift */; };
0E4909701FEAC1C6005CAA50 /* CallModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49096F1FEAC1C6005CAA50 /* CallModel.swift */; };
0E4909751FEAC943005CAA50 /* CallViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E4909741FEAC943005CAA50 /* CallViewController.storyboard */; };
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 */; };
0E983E6E1FC77C3E0082103E /* ConversationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E983E6D1FC77C3E0082103E /* ConversationModel.swift */; };
0E9D84491FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9D84481FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift */; };
......@@ -365,6 +368,9 @@
0E49096B1FEAB225005CAA50 /* CallsAdapterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsAdapterDelegate.swift; sourceTree = "<group>"; };
0E49096D1FEAC0DE005CAA50 /* CallsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsService.swift; sourceTree = "<group>"; };
0E49096F1FEAC1C6005CAA50 /* CallModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModel.swift; sourceTree = "<group>"; };
0E4909741FEAC943005CAA50 /* CallViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = CallViewController.storyboard; sourceTree = "<group>"; };
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>"; };
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>"; };
......@@ -944,6 +950,16 @@
path = DBHelpers;
sourceTree = "<group>";
};
0E4909711FEAC822005CAA50 /* Calls */ = {
isa = PBXGroup;
children = (
0E4909741FEAC943005CAA50 /* CallViewController.storyboard */,
0E4909791FEAC9E1005CAA50 /* CallViewController.swift */,
0E49097B1FEACA4B005CAA50 /* CallViewModel.swift */,
);
path = Calls;
sourceTree = "<group>";
};
0E5AFE0A1F8EBC040040D539 /* Cells */ = {
isa = PBXGroup;
children = (
......@@ -996,6 +1012,7 @@
isa = PBXGroup;
children = (
5CE66F721FBF765D00EE9291 /* InitialLoading */,
0E4909711FEAC822005CAA50 /* Calls */,
0E9D84471FA7D9EC00C561EB /* TabBar */,
0EDE34C51F868D2D00FFA15C /* Shared */,
1A0C4EBD1F1D48DD00550433 /* Walkthrough */,
......@@ -1463,6 +1480,7 @@
623660AA20092081002598C1 /* src in Resources */,
1A2D18B11F2915B600B2C785 /* SmartlistViewController.storyboard in Resources */,
0E403F831F7D79B000C80BC2 /* MessageCellGenerated.xib in Resources */,
0E4909751FEAC943005CAA50 /* CallViewController.storyboard in Resources */,
04399A031D1C2D9D00E99CD9 /* Images.xcassets in Resources */,
1A2041841F1EA0FC00C08435 /* CreateAccountViewController.storyboard in Resources */,
0E2D5F551F9145F200D574BF /* LinkNewDeviceCell.xib in Resources */,
......@@ -1600,6 +1618,7 @@
1A5DC0201F355DCF0075E8EF /* ContactsService.swift in Sources */,
1A2D18C71F29180700B2C785 /* DeviceModel.swift in Sources */,
1A20418F1F1EAC0E00C08435 /* Coordinator.swift in Sources */,
0E49097C1FEACA4B005CAA50 /* CallViewModel.swift in Sources */,
1A2D18A11F27A6D600B2C785 /* LinkDeviceViewController.swift in Sources */,
1A0C4EDC1F1D4B7E00550433 /* WelcomeViewController.swift in Sources */,
1A2D18D81F2918EE00B2C785 /* MeDetailViewController.swift in Sources */,
......@@ -1608,6 +1627,7 @@
56BBC99F1ED714CB00CDAF8B /* MessagesAdapter.mm in Sources */,
0E49096A1FEAB156005CAA50 /* CallsAdapter.mm in Sources */,
1A2D18A61F27F7A400B2C785 /* UIViewController+Rx.swift in Sources */,
0E49097A1FEAC9E1005CAA50 /* CallViewController.swift in Sources */,
1A5DC0241F3564360075E8EF /* ContactRequestModel.swift in Sources */,
0E4909701FEAC1C6005CAA50 /* CallModel.swift in Sources */,
1A5DC03F1F35678D0075E8EF /* ContactRequestsViewController.swift in Sources */,
......
......@@ -36,6 +36,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private let conversationsService = ConversationsService(withMessageAdapter: MessagesAdapter())
private let contactsService = ContactsService(withContactsAdapter: ContactsAdapter())
private let presenceService = PresenceService(withPresenceAdapter: PresenceAdapter())
private let callService = CallsService(withCallsAdapter: CallsAdapter())
private let networkService = NetworkService()
private var conversationManager: ConversationsManager?
private var contactRequestManager: ContactRequestManager?
......@@ -47,8 +48,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
withConversationService: self.conversationsService,
withContactsService: self.contactsService,
withPresenceService: self.presenceService,
withNetworkService: self.networkService
)
withNetworkService: self.networkService,
withCallService: self.callService)
}()
private lazy var appCoordinator: AppCoordinator = {
return AppCoordinator(with: self.injectionBag)
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ngv-XP-7A7">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Calls-->
<scene sceneID="XKD-ru-Nw9">
<objects>
<viewController title="Calls" id="ngv-XP-7A7" customClass="CallViewController" customModule="Ring" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="WrD-XI-6aI"/>
<viewControllerLayoutGuide type="bottom" id="4n1-G8-SAO"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="QpJ-Sx-9dG">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uC8-vY-dHO">
<rect key="frame" x="0.0" y="0.0" width="375" height="618"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="cOr-ft-BIO">
<rect key="frame" x="0.0" y="0.0" width="375" height="618"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<blurEffect style="light"/>
</visualEffectView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="B3b-V0-rjx">
<rect key="frame" x="157.5" y="538" width="60" height="60"/>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" constant="60" id="oOU-Hx-5HZ"/>
<constraint firstAttribute="height" constant="60" id="s7U-o7-NEg"/>
</constraints>
<state key="normal" title="Cancel">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
<real key="value" value="30"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="roundedCorners" value="YES"/>
</userDefinedRuntimeAttributes>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_contact_picture" translatesAutoresizingMaskIntoConstraints="NO" id="fnt-PQ-Q6P">
<rect key="frame" x="137.5" y="64" width="100" height="100"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="Miw-Nd-4Fa"/>
<constraint firstAttribute="height" constant="100" id="V9c-7W-Frv"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
<real key="value" value="50"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="73Y-N1-Yga">
<rect key="frame" x="187.5" y="172" width="0.0" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="26"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SdV-jx-Mla">
<rect key="frame" x="187.5" y="530" width="0.0" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zMN-6z-uXT">
<rect key="frame" x="187.5" y="188" width="0.0" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="zMN-6z-uXT" firstAttribute="centerX" secondItem="QpJ-Sx-9dG" secondAttribute="centerX" id="8Mt-nX-xlY"/>
<constraint firstItem="zMN-6z-uXT" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="QpJ-Sx-9dG" secondAttribute="leading" constant="8" id="Bf4-J4-K9c"/>
<constraint firstItem="fnt-PQ-Q6P" firstAttribute="top" secondItem="WrD-XI-6aI" secondAttribute="bottom" constant="44" id="C6d-Dz-lnR"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="SdV-jx-Mla" secondAttribute="trailing" constant="8" id="EDd-Cg-QHP"/>
<constraint firstItem="B3b-V0-rjx" firstAttribute="centerX" secondItem="QpJ-Sx-9dG" secondAttribute="centerX" id="Foq-ZE-uj9"/>
<constraint firstItem="uC8-vY-dHO" firstAttribute="leading" secondItem="QpJ-Sx-9dG" secondAttribute="leading" id="G08-ef-Ucc"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="73Y-N1-Yga" secondAttribute="trailing" constant="8" id="Gcb-08-NRr"/>
<constraint firstItem="4n1-G8-SAO" firstAttribute="top" secondItem="B3b-V0-rjx" secondAttribute="bottom" constant="20" id="HwS-Ng-Ojz"/>
<constraint firstItem="73Y-N1-Yga" firstAttribute="top" secondItem="fnt-PQ-Q6P" secondAttribute="bottom" constant="8" id="JC6-KJ-L8L"/>
<constraint firstItem="SdV-jx-Mla" firstAttribute="centerX" secondItem="QpJ-Sx-9dG" secondAttribute="centerX" id="MXS-7j-cD5"/>
<constraint firstItem="uC8-vY-dHO" firstAttribute="top" secondItem="QpJ-Sx-9dG" secondAttribute="top" id="Rse-54-gPI"/>
<constraint firstItem="zMN-6z-uXT" firstAttribute="top" secondItem="73Y-N1-Yga" secondAttribute="bottom" constant="16" id="YQp-tl-h73"/>
<constraint firstItem="SdV-jx-Mla" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="QpJ-Sx-9dG" secondAttribute="leading" constant="8" id="Zms-si-GOc"/>
<constraint firstItem="fnt-PQ-Q6P" firstAttribute="centerX" secondItem="QpJ-Sx-9dG" secondAttribute="centerX" id="b3O-Sw-To4"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="zMN-6z-uXT" secondAttribute="trailing" constant="8" id="cZ0-u0-t7T"/>
<constraint firstItem="B3b-V0-rjx" firstAttribute="top" secondItem="SdV-jx-Mla" secondAttribute="bottom" constant="8" id="dCo-8J-8Ba"/>
<constraint firstItem="4n1-G8-SAO" firstAttribute="top" secondItem="uC8-vY-dHO" secondAttribute="bottom" id="iVJ-Fo-imi"/>
<constraint firstItem="73Y-N1-Yga" firstAttribute="centerX" secondItem="QpJ-Sx-9dG" secondAttribute="centerX" id="p8J-P2-tcm"/>
<constraint firstItem="73Y-N1-Yga" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="QpJ-Sx-9dG" secondAttribute="leading" constant="8" id="ryb-Wh-KM2"/>
<constraint firstAttribute="trailing" secondItem="uC8-vY-dHO" secondAttribute="trailing" id="tmf-Ae-VCF"/>
</constraints>
</view>
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="cancelButton" destination="B3b-V0-rjx" id="dU9-MG-0y3"/>
<outlet property="durationLabel" destination="zMN-6z-uXT" id="Uuf-ph-lrC"/>
<outlet property="infoBottomLabel" destination="SdV-jx-Mla" id="yX9-em-p4w"/>
<outlet property="nameLabel" destination="73Y-N1-Yga" id="XcQ-V6-ZrF"/>
<outlet property="profileImageView" destination="fnt-PQ-Q6P" id="MgB-Ev-bTc"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="OFk-0u-Pap" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-74.400000000000006" y="131.78410794602701"/>
</scene>
</scenes>
<resources>
<image name="ic_contact_picture" width="128" height="128"/>
</resources>
</document>
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
* 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 Chameleon
import RxSwift
import Reusable
class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
@IBOutlet weak var profileImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var durationLabel: UILabel!
@IBOutlet weak var infoBottomLabel: UILabel!
@IBOutlet weak var cancelButton: UIButton!
var viewModel: CallViewModel!
fileprivate let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
self.setupUI()
self.setupBindings()
}
func setupUI() {
self.cancelButton.backgroundColor = UIColor.red
}
func setupBindings() {
//Cancel button action
self.cancelButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.removeFromScreen()
self?.viewModel.cancelCall()
}).disposed(by: self.disposeBag)
//Data bindings
self.viewModel.dismisVC
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] dismiss in
if dismiss {
self?.removeFromScreen()
}
}).disposed(by: self.disposeBag)
self.viewModel.contactName
.observeOn(MainScheduler.instance)
.bind(to: self.nameLabel.rx.text)
.disposed(by: self.disposeBag)
self.viewModel.callDuration
.observeOn(MainScheduler.instance)
.bind(to: self.durationLabel.rx.text)
.disposed(by: self.disposeBag)
self.viewModel.bottomInfo
.observeOn(MainScheduler.instance)
.bind(to: self.infoBottomLabel.rx.text)
.disposed(by: self.disposeBag)
}
func removeFromScreen() {
self.dismiss(animated: false)
}
}
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
* 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
import SwiftyBeaver
import Contacts
class CallViewModel: Stateable, ViewModel {
//stateable
private let stateSubject = PublishSubject<State>()
lazy var state: Observable<State> = {
return self.stateSubject.asObservable()
}()
fileprivate let callService: CallsService
fileprivate let contactsService: ContactsService
fileprivate let accountService: AccountsService
private let disposeBag = DisposeBag()
fileprivate let log = SwiftyBeaver.self
var call: CallModel?
// data for ViewCintroller binding
lazy var dismisVC: Observable<Bool> = {
return callService.currentCall.map({[weak self] call in
return call.state == .over || call.state == .failure && call.callId == self?.call?.callId
}).map({ hide in
return hide
})
}()
lazy var contactName: Observable<String> = {
return callService.currentCall.filter({ [weak self] call in
return call.state != .over && call.state != .inactive && call.callId == self?.call?.callId
}).map({ call in
if !call.displayName.isEmpty {
return call.displayName
} else if !call.registeredName.isEmpty {
return call.registeredName
} else {
return L10n.Calls.unknown
}
})
}()
lazy var callDuration: Observable<String> = {
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.takeUntil(self.callService.currentCall
.filter { [weak self] call in
call.state == .over &&
call.callId == self?.call?.callId
})
.map({ elapsed in
return CallViewModel.formattedDurationFrom(interval: elapsed)
})
return self.callService.currentCall.filter({ call in
return call.state == .current
}).flatMap({ _ in
return timer
})
}()
lazy var bottomInfo: Observable<String> = {
return callService.currentCall.map({ [weak self] call in
if call.state == .connecting || call.state == .ringing && call.callType == .outgoing && call.callId == self?.call?.callId {
return L10n.Calls.calling
} else if call.state == .over {
return L10n.Calls.callFinished
} else {
return ""
}
})
}()
required init(with injectionBag: InjectionBag) {
self.callService = injectionBag.callService
self.contactsService = injectionBag.contactsService
self.accountService = injectionBag.accountService
}
static func formattedDurationFrom(interval: Int) -> String {
let seconds = interval % 60
let minutes = (interval / 60) % 60
let hours = (interval / 3600)
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
func cancelCall() {
guard let call = self.call else {
return
}
self.callService.hangUp(callId: call.callId)
.subscribe(onCompleted: { [weak self] in
self?.log.info("Call canceled")
}, onError: { [weak self] error in
self?.log.error("Failed to cancel the call")
}).disposed(by: self.disposeBag)
}
func answerCall() {
guard let call = self.call else {
return
}
self.callService.accept(callId: call.callId)
.subscribe(onCompleted: { [weak self] in
self?.log.info("Call answered")
}, onError: { [weak self] error in
self?.log.error("Failed to answer the call")
}).disposed(by: self.disposeBag)
}
func placeCall(with uri: String, userName: String) {
guard let account = self.accountService.currentAccount else {
return
}
self.callService.placeCall(withAccount: account,
toRingId: uri,
userName: userName)
.subscribe(onSuccess: { [unowned self] callModel in
self.call = callModel
self.log.info("Call placed: \(callModel.callId)")
}, onError: { [unowned self] error in
self.log.error("Failed to place the call")
}).disposed(by: self.disposeBag)
}
}
......@@ -50,6 +50,7 @@ enum Asset {
static let addPerson = ImageAsset(name: "add_person")
static let backgroundRing = ImageAsset(name: "background_ring")
static let blockIcon = ImageAsset(name: "block_icon")
static let callButton = ImageAsset(name: "call_button")
static let contactRequestIcon = ImageAsset(name: "contact_request_icon")
static let conversationIcon = ImageAsset(name: "conversation_icon")
static let device = ImageAsset(name: "device")
......@@ -66,6 +67,7 @@ enum Asset {
addPerson,
backgroundRing,
blockIcon,
callButton,
contactRequestIcon,
conversationIcon,
device,
......
......@@ -50,6 +50,11 @@ extension UIViewController {
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
enum StoryboardScene {
enum CallViewController: StoryboardType {
static let storyboardName = "CallViewController"
static let initialScene = InitialSceneType<Ring.CallViewController>(storyboard: CallViewController.self)
}
enum ContactRequestsViewController: StoryboardType {
static let storyboardName = "ContactRequestsViewController"
......
......@@ -51,6 +51,12 @@ enum L10n {
static let dbFailedMessage = L10n.tr("Localizable", "alerts.dbFailedMessage")
/// An error happned when launching Ring
static let dbFailedTitle = L10n.tr("Localizable", "alerts.dbFailedTitle")
/// Incoming call from
static let incomingCallAllertTitle = L10n.tr("Localizable", "alerts.incomingCallAllertTitle")
/// Accept
static let incomingCallButtonAccept = L10n.tr("Localizable", "alerts.incomingCallButtonAccept")
/// Ignore
static let incomingCallButtonIgnore = L10n.tr("Localizable", "alerts.incomingCallButtonIgnore")
/// Cancel
static let profileCancelPhoto = L10n.tr("Localizable", "alerts.profileCancelPhoto")
/// Take photo
......@@ -59,6 +65,19 @@ enum L10n {
static let profileUploadPhoto = L10n.tr("Localizable", "alerts.profileUploadPhoto")
}
enum Calls {
/// Call finished
static let callFinished = L10n.tr("Localizable", "calls.callFinished")
/// Calling...
static let calling = L10n.tr("Localizable", "calls.calling")
/// Call
static let callItemTitle = L10n.tr("Localizable", "calls.callItemTitle")
/// wants to talk to you
static let incomingCallInfo = L10n.tr("Localizable", "calls.incomingCallInfo")
/// Unknown
static let unknown = L10n.tr("Localizable", "calls.unknown")
}
enum Createaccount {
/// Choose strong password you will remember to protect your Ring account.
static let chooseStrongPassword = L10n.tr("Localizable", "createAccount.chooseStrongPassword")
......
......@@ -30,6 +30,7 @@ class InjectionBag {
let contactsService: ContactsService
let presenceService: PresenceService
let networkService: NetworkService
let callService: CallsService
init (withDaemonService daemonService: DaemonService,
withAccountService accountService: AccountsService,
......@@ -37,7 +38,8 @@ class InjectionBag {
withConversationService conversationService: ConversationsService,
withContactsService contactsService: ContactsService,
withPresenceService presenceService: PresenceService,
withNetworkService networkService: NetworkService) {
withNetworkService networkService: NetworkService,
withCallService callService: CallsService) {
self.daemonService = daemonService
self.accountService = accountService
self.nameService = nameService
......@@ -45,6 +47,7 @@ class InjectionBag {
self.contactsService = contactsService
self.presenceService = presenceService
self.networkService = networkService
self.callService = callService
}
}
......@@ -46,6 +46,8 @@ class ContactRequestsCoordinator: Coordinator, StateableResponsive {
switch state {
case .conversationDetail (let conversationViewModel):
self.showConversation(withConversationViewModel: conversationViewModel)
default:
break
}
}).disposed(by: self.disposeBag)
}
......
......@@ -107,14 +107,19 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo
self.tableView.contentInset.bottom = messageAccessoryView.frame.size.height
self.tableView.scrollIndicatorInsets.bottom = messageAccessoryView.frame.size.height
//invite button
//set navigation buttons - call and send contact request
let inviteItem = UIBarButtonItem()
inviteItem.image = UIImage(named: "add_person")
inviteItem.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] in
self.inviteItemTapped()
}).disposed(by: self.disposeBag)
let callItem = UIBarButtonItem()
callItem.image = UIImage(asset: Asset.callButton)
callItem.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] in
self.placeCall()
}).disposed(by: self.disposeBag)
self.viewModel.inviteButtonIsAvailable.asObservable().bind(to: inviteItem.rx.isEnabled).disposed(by: disposeBag)
//block contact button
......@@ -125,13 +130,14 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo
self.blockItemTapped()
}).disposed(by: self.disposeBag)
self.navigationItem.rightBarButtonItems = [blockItem, inviteItem]
self.navigationItem.rightBarButtonItems = [blockItem, inviteItem, callItem]
Observable<[UIBarButtonItem]>