blob: 6ffaa7da679fcd6e7907cfe4d283de4b653b76a8 [file] [log] [blame]
/*
* Copyright (C) 2018-2019 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
* Author: Quentin Muret <quentin.muret@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 RxRelay
enum ConversationState: State {
case startCall(contactRingId: String, userName: String)
case startAudioCall(contactRingId: String, userName: String)
case conversationDetail(conversationViewModel: ConversationViewModel)
case contactDetail(conversationViewModel: ConversationModel)
case qrCode
case createSwarm
case createNewAccount
case showDialpad(inCall: Bool)
case showGeneralSettings
case recordFile(conversation: ConversationModel, audioOnly: Bool)
case navigateToCall(call: CallModel)
case showContactPicker(callID: String, contactSelectedCB: ((_ contact: [ConferencableItem]) -> Void)?, conversationSelectedCB: ((_ conversaionIds: [String]?) -> Void)?)
case openConversationFromCall(conversation: ConversationModel)
case needAccountMigration(accountId: String)
case accountModeChanged
case openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect, delegate: PreviewViewControllerDelegate)
case openIncomingInvitationView(displayName: String, request: RequestModel, parentView: UIViewController, invitationHandeledCB: ((_ conversationId: String) -> Void))
case openOutgoingInvitationView(displayName: String,
alias: String,
avatar: Data?,
contactJamiId: String,
accountId: String,
parentView: UIViewController,
invitationHandeledCB: ((_ conversationId: String) -> Void))
case showAccountSettings
case accountRemoved
case needToOnboard
case returnToSmartList
case migrateAccount(accountId: String)
case presentSwarmInfo(swarmInfo: SwarmInfoProtocol)
case openConversation(jamiId: String)
case openConversationForConversationId(conversationId: String, accountId: String, shouldOpenSmarList: Bool)
case reopenCall(viewController: CallViewController)
case openAboutJami
}
protocol ConversationNavigation: AnyObject {
var injectionBag: InjectionBag { get }
func addLockFlags()
}
extension ConversationNavigation where Self: Coordinator, Self: StateableResponsive {
// swiftlint:disable cyclomatic_complexity
func callbackPlaceCall() {
self.stateSubject
.subscribe(onNext: { [weak self] (state) in
guard let self = self, let state = state as? ConversationState else { return }
switch state {
case .startCall(let contactRingId, let name):
self.startOutgoingCall(contactRingId: contactRingId, userName: name)
case .startAudioCall(let contactRingId, let name):
self.startOutgoingCall(contactRingId: contactRingId, userName: name, isAudioOnly: true)
case .conversationDetail(let conversationViewModel):
self.showConversation(withConversationViewModel: conversationViewModel)
case .contactDetail(let conversationModel):
self.presentContactInfo(conversation: conversationModel)
case .qrCode:
self.openQRCode()
case .createSwarm:
self.createSwarm()
case .recordFile(let conversation, let audioOnly):
self.openRecordFile(conversation: conversation, audioOnly: audioOnly)
case .navigateToCall(let call):
self.navigateToCall(call: call)
case .needAccountMigration(let accountId):
self.migrateAccount(accountId: accountId)
case .openFullScreenPreview(let parentView, let viewModel, let image, let initialFrame, let delegate):
self.openFullScreenPreview(parentView: parentView, viewModel: viewModel, image: image, initialFrame: initialFrame, delegate: delegate)
case .openIncomingInvitationView(let displayName, let request, let parentView, let invitationHandeledCB):
self.openIncomingInvitationView(displayName: displayName, request: request, parentView: parentView, invitationHandeledCB: invitationHandeledCB)
case .openOutgoingInvitationView(let displayName, let alias, let avatar, let contactJamiId, let accountId, let parentView, let invitationHandeledCB):
self.openOutgoingInvitationView(displayName: displayName,
alias: alias,
avatar: avatar,
contactJamiId: contactJamiId,
accountId: accountId,
parentView: parentView,
invitationHandeledCB: invitationHandeledCB)
case .presentSwarmInfo(let swarmInfo):
self.presentSwarmInfo(swarmInfo: swarmInfo)
case .openConversationForConversationId:
break
case .reopenCall(let viewController):
self.reopenCall(viewController: viewController)
default:
break
}
})
.disposed(by: self.disposeBag)
}
func migrateAccount(accountId: String) {
if let parent = self.parentCoordinator as? AppCoordinator {
parent.stateSubject.onNext(AppState.needAccountMigration(accountId: accountId))
}
}
func openRecordFile(conversation: ConversationModel, audioOnly: Bool) {
let recordFileViewController = SendFileViewController.instantiate(with: self.injectionBag)
recordFileViewController.viewModel.conversation = conversation
recordFileViewController.viewModel.audioOnly = audioOnly
self.present(viewController: recordFileViewController,
withStyle: .popup,
withAnimation: !audioOnly,
withStateable: recordFileViewController.viewModel)
}
func openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect, delegate: PreviewViewControllerDelegate) {
if viewModel == nil && image == nil { return }
let previewController = PreviewViewController.instantiate(with: self.injectionBag)
previewController.delegate = delegate
if let viewModel = viewModel {
previewController.viewModel.playerViewModel = viewModel
previewController.type = .player
} else if let image = image {
previewController.viewModel.image = image
previewController.type = .image
}
parentView.addChildController(previewController, initialFrame: initialFrame)
previewController.playerView?.sizeMode = .fullScreen
}
func openIncomingInvitationView(displayName: String, request: RequestModel, parentView: UIViewController, invitationHandeledCB: @escaping ((_ conversationId: String) -> Void)) {
let invitationVC = InvitationViewController.instantiate(with: self.injectionBag)
invitationVC.viewModel.setInfoForRequest(request: request, displayName: displayName, invitationHandeledCB: invitationHandeledCB)
parentView.addChildController(invitationVC, initialFrame: parentView.view.bounds)
}
func openOutgoingInvitationView(displayName: String, alias: String, avatar: Data?, contactJamiId: String, accountId: String, parentView: UIViewController, invitationHandeledCB: @escaping ((_ conversationId: String) -> Void)) {
let invitationVC = InvitationViewController.instantiate(with: self.injectionBag)
invitationVC.viewModel.setInfoForSearchResult(contactJamiId: contactJamiId,
accountId: accountId,
displayName: displayName,
alias: alias,
avatar: avatar,
invitationHandeledCB: invitationHandeledCB)
parentView.addChildController(invitationVC, initialFrame: parentView.view.bounds)
}
func openQRCode () {
let scanViewController = ScanViewController.instantiate(with: self.injectionBag)
self.present(viewController: scanViewController,
withStyle: .present,
withAnimation: true,
withStateable: scanViewController.viewModel)
}
func createSwarm() {
let swarmCreationViewController = SwarmCreationViewController.instantiate(with: self.injectionBag)
self.present(viewController: swarmCreationViewController,
withStyle: .show,
withAnimation: true,
withStateable: swarmCreationViewController.viewModel)
}
func presentSwarmInfo(swarmInfo: SwarmInfoProtocol) {
if let flag = self.presentingVC[VCType.contact.rawValue], flag {
return
}
self.presentingVC[VCType.contact.rawValue] = true
let swarmInfoViewController = SwarmInfoViewController.instantiate(with: self.injectionBag)
swarmInfoViewController.viewModel.swarmInfo = swarmInfo
self.present(viewController: swarmInfoViewController,
withStyle: .show,
withAnimation: true,
withStateable: swarmInfoViewController.viewModel,
lockWhilePresenting: VCType.contact.rawValue)
}
func presentContactInfo(conversation: ConversationModel) {
if let flag = self.presentingVC[VCType.contact.rawValue], flag {
return
}
self.presentingVC[VCType.contact.rawValue] = true
let contactViewController = ContactViewController.instantiate(with: self.injectionBag)
contactViewController.viewModel.conversation = conversation
self.present(viewController: contactViewController,
withStyle: .show,
withAnimation: true,
withStateable: contactViewController.viewModel,
lockWhilePresenting: VCType.contact.rawValue)
}
func showConversation (withConversationViewModel conversationViewModel: ConversationViewModel) {
if let flag = self.presentingVC[VCType.conversation.rawValue], flag {
return
}
self.presentingVC[VCType.conversation.rawValue] = true
let conversationViewController = ConversationViewController.instantiate(with: self.injectionBag)
conversationViewController.viewModel = conversationViewModel
self.present(viewController: conversationViewController,
withStyle: .show,
withAnimation: false,
withStateable: conversationViewController.viewModel,
lockWhilePresenting: VCType.conversation.rawValue)
}
func reopenCall(viewController: CallViewController) {
guard let call = viewController.viewModel.call else { return }
if self.tryPresentCallFromStack(call: call) {
return
}
if !dismissTopCallViewControllerIfNeeded() {
return
}
self.present(viewController: viewController,
withStyle: .appear,
withAnimation: false,
withStateable: viewController.viewModel)
}
func tryPresentCallFromStack(call: CallModel) -> Bool {
guard let navController = self.rootViewController as? UINavigationController else { return false
}
let controllers = navController.children
for controller in controllers
where controller.isKind(of: (CallViewController).self) {
if let callController = controller as? CallViewController, callController.viewModel.call?.callId == call.callId {
navController.popToViewController(callController, animated: true)
return true
}
}
return false
}
func dismissTopCallViewControllerIfNeeded() -> Bool {
guard let topController = getTopController(),
!topController.isKind(of: CallViewController.self) else {
return false
}
topController.dismiss(animated: false, completion: nil)
return true
}
func navigateToCall(call: CallModel) {
if self.tryPresentCallFromStack(call: call) {
return
}
if !dismissTopCallViewControllerIfNeeded() {
return
}
let callViewController = CallViewController
.instantiate(with: self.injectionBag)
callViewController.viewModel.call = call
self.present(viewController: callViewController,
withStyle: .appear,
withAnimation: false,
withStateable: callViewController.viewModel)
}
func getTopController() -> UIViewController? {
guard var topController = UIApplication.shared
.keyWindow?.rootViewController else {
return nil
}
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
func startOutgoingCall(contactRingId: String, userName: String, isAudioOnly: Bool = false) {
guard let topController = getTopController(),
!topController.isKind(of: (CallViewController).self),
let account = self.injectionBag.accountService.currentAccount else {
return
}
DispatchQueue.main.async {
topController.dismiss(animated: false, completion: nil)
let callViewController = CallViewController.instantiate(with: self.injectionBag)
self.present(viewController: callViewController,
withStyle: .appear,
withAnimation: false,
withStateable: callViewController.viewModel)
callViewController.viewModel.placeCall(with: contactRingId, userName: userName, account: account, isAudioOnly: isAudioOnly)
}
}
}