ConversationsCoordinator.swift 11.4 KB
Newer Older
1
/*
2
 *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
3 4
 *
 *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
5
 *  Author: Quentin Muret <quentin.muret@savoirfairelinux.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 *  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
import RxSwift

/// This Coordinator drives the conversation navigation (Smartlist / Conversation detail)
26
class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNavigation {
27
    var presentingVC = [String: Bool]()
28 29 30 31 32 33

    var rootViewController: UIViewController {
        return self.navigationViewController
    }

    var childCoordinators = [Coordinator]()
34
    var parentCoordinator: Coordinator?
35

36
    private let navigationViewController = BaseViewController(with: TabBarItemType.chat)
37
    let injectionBag: InjectionBag
38 39 40
    let disposeBag = DisposeBag()

    let stateSubject = PublishSubject<State>()
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
41
    let callService: CallsService
42 43
    let accountService: AccountsService
    let conversationService: ConversationsService
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
44
    let callsProvider: CallsProviderDelegate
45 46 47

    required init (with injectionBag: InjectionBag) {
        self.injectionBag = injectionBag
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
48 49

        self.callService = injectionBag.callService
50 51
        self.accountService = injectionBag.accountService
        self.conversationService = injectionBag.conversationsService
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
52
        self.callsProvider = injectionBag.callsProvider
53
        self.addLockFlags()
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
54

55 56 57 58 59
        self.stateSubject.subscribe(onNext: { [unowned self] (state) in
            guard let state = state as? ConversationState else { return }
            switch state {
            case .createNewAccount:
                self.createNewAccount()
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
60 61
            case .showDialpad(let inCall):
                self.showDialpad(inCall: inCall)
62 63
            case .showGeneralSettings:
                self.showGeneralSettings()
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
64
            case .navigateToCall(let call):
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
65
                self.presentCallController(call: call)
66 67 68 69 70
            default:
                break
            }
        }).disposed(by: self.disposeBag)

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
71 72 73
        self.callService.newCall
            .asObservable()
            .observeOn(MainScheduler.instance)
74
            .subscribe(onNext: { (call) in
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
75
                self.showIncomingCall(call: call)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
76
            }).disposed(by: self.disposeBag)
77
        self.navigationViewController.viewModel = ChatTabBarItemViewModel(with: self.injectionBag)
78
        self.callbackPlaceCall()
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
79 80
        //for iOS version less than 10 support open call from notification
        NotificationCenter.default.addObserver(self, selector: #selector(self.answerIncomingCall(_:)), name: NSNotification.Name(NotificationName.answerCallFromNotifications.rawValue), object: nil)
81 82 83 84 85 86

        self.accountService.currentAccountChanged
            .subscribe(onNext: {[unowned self] _ in
                self.navigationViewController.viewModel =
                    ChatTabBarItemViewModel(with: self.injectionBag)
            }).disposed(by: self.disposeBag)
87 88
    }

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    /*
    * when receive a new call trigger CallKit for iOS 10 and than navigate to
    * call controller when call accepted. For iOS less than 10 present
    * call controller or trigger notifications, depending of current app state
    */
    func showIncomingCall(call: CallModel) {
        guard let account = self.accountService
            .getAccount(fromAccountId: call.accountId),
            !call.callId.isEmpty else {return}
        guard let topController = getTopController(),
            !topController.isKind(of: (CallViewController).self) else {
                return
        }
        let callViewController = CallViewController
            .instantiate(with: self.injectionBag)
        callViewController.viewModel.call = call

        var tempBag = DisposeBag()
107
        if #available(iOS 10.0, *) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
            call.callUUID = UUID()
            callsProvider
                .reportIncomingCall(account: account, call: call) { _ in
                                        // if starting CallKit failed fallback to jami call screen
                                        if UIApplication.shared.applicationState != .active {
                                            if AccountModelHelper
                                                .init(withAccount: account).isAccountSip() ||
                                                !self.accountService.getCurrentProxyState(accountID: account.id) {
                                                return
                                            }
                                            self.triggerCallNotifications(call: call)
                                            return
                                        }
                                        topController
                                            .present(callViewController,
                                                     animated: true,
                                                     completion: nil)
            }
            callsProvider.sharedResponseStream
                .filter({ serviceEvent in
                    if serviceEvent.eventType != ServiceEventType.callProviderAnswerCall {
                        return false
                    }
                    guard let callUUID: String = serviceEvent
                        .getEventInput(ServiceEventInput.callUUID) else {return false}
                    return callUUID == call.callUUID.uuidString
                }).subscribe(onNext: { _ in
                    topController.present(callViewController, animated: true, completion: nil)
                    tempBag = DisposeBag()
                }).disposed(by: tempBag)
            callViewController.viewModel.dismisVC
                .share()
                .subscribe(onNext: { hide in
                    if hide {
                tempBag = DisposeBag()
                    }
            }).disposed(by: tempBag)

        } else {
            if UIApplication.shared.applicationState != .active {
                if AccountModelHelper
                    .init(withAccount: account).isAccountSip() ||
                    !self.accountService.getCurrentProxyState(accountID: account.id) {
                    return
                }
                triggerCallNotifications(call: call)
154
                return
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
155 156
            }
            topController.present(callViewController, animated: true, completion: nil)
157
        }
158 159
    }

160 161 162 163 164 165
    func createNewAccount() {
        if let parent = self.parentCoordinator as? AppCoordinator {
            parent.stateSubject.onNext(AppState.addAccount)
        }
    }

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    func showDialpad(inCall: Bool) {
        let dialpadViewController = DialpadViewController.instantiate(with: self.injectionBag)
        dialpadViewController.viewModel.inCallDialpad = inCall
        if !inCall {
            self.present(viewController: dialpadViewController,
                         withStyle: .present,
                         withAnimation: true,
                         withStateable: dialpadViewController.viewModel)
            return
        }
        if let controller = self.navigationViewController.visibleViewController as? CallViewController {
            controller.present(dialpadViewController, animated: true, completion: nil)
        }
    }

181 182 183 184 185
    func showGeneralSettings() {
        let settingsViewController = GeneralSettingsViewController.instantiate(with: self.injectionBag)
        self.present(viewController: settingsViewController, withStyle: .present, withAnimation: true, disposeBag: self.disposeBag)
    }

186 187 188 189 190
    func puchConversation(participantId: String) {
        let conversationViewModel = ConversationViewModel(with: self.injectionBag)
        guard let account = accountService.currentAccount else {
            return
        }
191 192 193 194
        guard let uriString = JamiURI(schema: URIType.ring, infoHach: participantId).uriString else {
            return
        }
        guard let conversation = self.conversationService.findConversation(withUri: uriString, withAccountId: account.id) else {
195 196 197 198 199 200
            return
        }
        conversationViewModel.conversation = Variable<ConversationModel>(conversation)
        self.pushConversation(withConversationViewModel: conversationViewModel)
    }

201 202 203 204 205
    func start () {
        let smartListViewController = SmartlistViewController.instantiate(with: self.injectionBag)
        self.present(viewController: smartListViewController, withStyle: .show, withAnimation: true, withStateable: smartListViewController.viewModel)
    }

206 207 208 209 210
    func addLockFlags() {
        presentingVC[VCType.contact.rawValue] = false
        presentingVC[VCType.conversation.rawValue] = false
    }

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
    //open call controller when button navigate to call pressed
    func presentCallController (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
                }
        }
        guard let topController = getTopController(),
            !topController.isKind(of: (CallViewController).self) else {
                return
        }
        let callViewController = CallViewController
            .instantiate(with: self.injectionBag)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
230
        callViewController.viewModel.call = call
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
231
        topController.present(callViewController, animated: true, completion: nil)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
232 233
    }

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
234 235 236 237
    func triggerCallNotifications(call: CallModel) {
        var data = [String: String]()
        data [NotificationUserInfoKeys.name.rawValue] = call.displayName
        data [NotificationUserInfoKeys.callID.rawValue] = call.callId
238
        data [NotificationUserInfoKeys.accountID.rawValue] = call.accountId
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
239 240 241 242 243 244 245 246 247 248
        let helper = LocalNotificationsHelper()
        helper.presentCallNotification(data: data, callService: self.callService)
    }

// MARK: - iOS 9.3 - 10

    @objc func answerIncomingCall(_ notification: NSNotification) {
        guard let callid = notification.userInfo?[NotificationUserInfoKeys.callID.rawValue] as? String,
            let call = self.callService.call(callID: callid) else {
                return
249
        }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
250 251 252 253 254 255 256 257 258
        let callViewController = CallViewController.instantiate(with: self.injectionBag)
        callViewController.viewModel.call = call
        callViewController.viewModel.answerCall()
            .subscribe(onCompleted: { [weak self] in
                self?.present(viewController: callViewController,
                              withStyle: .present,
                              withAnimation: false,
                              withStateable: callViewController.viewModel)
            }).disposed(by: self.disposeBag)
259 260
    }
}