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

/// Represents Application global navigation state
///
28
/// - initialLoading: the app should display the loading interface as navigation root
29
/// - needToOnboard: user has to onboard because he has no account
30
/// - allSet: everything is set, the app should display its main interface
31
public enum AppState: State {
32
    case initialLoading
33 34
    case needToOnboard(animated: Bool, isFirstAccount: Bool)
    case addAccount
35
    case allSet
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
36
    case accountRemoved
37 38
}

39 40 41 42 43 44
public enum VCType: String {
    case conversation
    case contact
    case blockList
}

45 46 47
/// This Coordinator drives the global navigation of the app: it can present the main interface, the
/// walkthrough or a loading interface
final class AppCoordinator: Coordinator, StateableResponsive {
48
    var presentingVC = [String: Bool]()
49

50
    // MARK: Coordinator
51
    var rootViewController: UIViewController {
52
        return self.navigationController
53
    }
54
    var parentCoordinator: Coordinator?
55 56

    var childCoordinators = [Coordinator]()
57
    // MARK: -
58

59
    // MARK: StateableResponsive
60 61 62
    let disposeBag = DisposeBag()

    let stateSubject = PublishSubject<State>()
63 64 65 66 67 68 69
    // MARK: -

    // MARK: Private members
    private let navigationController = UINavigationController()
    private let tabBarViewController = UITabBarController()
    private let injectionBag: InjectionBag
    private var mainInterfaceReady = false
70

71 72 73
    /// Initializer
    ///
    /// - Parameter injectionBag: the injected injectionBag
74 75 76
    required init (with injectionBag: InjectionBag) {
        self.injectionBag = injectionBag

77 78 79
        self.navigationController.setNavigationBarHidden(true, animated: false)
        self.prepareMainInterface()

80 81 82
        self.stateSubject.subscribe(onNext: { [unowned self] (state) in
            guard let state = state as? AppState else { return }
            switch state {
83 84
            case .initialLoading:
                self.showInitialLoading()
85 86
            case .needToOnboard(let animated, let isFirstAccount):
                self.showWalkthrough(animated: animated, isAccountFirst: isFirstAccount)
87 88
            case .allSet:
                self.showMainInterface()
89 90
            case .addAccount:
                self.showWalkthrough(animated: false, isAccountFirst: false)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
91 92
            case .accountRemoved:
                self.accountRemoved()
93 94 95 96
            }
        }).disposed(by: self.disposeBag)
    }

97
    /// Starts the coordinator
98
    func start () {
99 100 101 102 103
        //~ By default, always present the initial loading at start
        self.stateSubject.onNext(AppState.initialLoading)
        //~ Dispatch to the proper screen
        self.dispatchApplication()
    }
104

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
105 106 107 108
    func accountRemoved() {
        self.tabBarViewController.selectedIndex = 0
    }

109 110
    /// Handles the switch between the three supported screens.
    private func dispatchApplication() {
111
        if self.injectionBag.accountService.accounts.isEmpty {
112
            self.stateSubject.onNext(AppState.needToOnboard(animated: true, isFirstAccount: true))
113 114 115
        } else {
             self.stateSubject.onNext(AppState.allSet)
        }
116
    }
117

118 119 120 121 122
    // MARK: - Private methods
    /// Presents the initial loading interface as the root of the navigation
    private func showInitialLoading () {
        let initialLoading = InitialLoadingViewController.instantiate()
        self.navigationController.setViewControllers([initialLoading], animated: true)
123 124
    }

125 126 127 128
    func showDatabaseError() {
        let alertController = UIAlertController(title: L10n.Alerts.dbFailedTitle,
                                                message: L10n.Alerts.dbFailedMessage,
                                                preferredStyle: .alert)
129
        self.present(viewController: alertController, withStyle: .present, withAnimation: false, disposeBag: self.disposeBag)
130 131
    }

132
    /// Presents the walkthrough as a popup with a fade effect
133
    private func showWalkthrough (animated: Bool, isAccountFirst: Bool) {
134
        let walkthroughCoordinator = WalkthroughCoordinator(with: self.injectionBag)
135 136
        walkthroughCoordinator.isAccountFirst = isAccountFirst
        walkthroughCoordinator.withAnimations = animated
137 138
        walkthroughCoordinator.start()

139 140
        self.addChildCoordinator(childCoordinator: walkthroughCoordinator)
        let walkthroughViewController = walkthroughCoordinator.rootViewController
141 142
        self.present(viewController: walkthroughViewController,
                     withStyle: .appear,
143 144
                     withAnimation: true,
                     disposeBag: self.disposeBag)
145

146
        walkthroughViewController.rx.controllerWasDismissed.subscribe(onNext: { [weak self, weak walkthroughCoordinator] (_) in
147 148
            walkthroughCoordinator?.stateSubject.dispose()
            self?.removeChildCoordinator(childCoordinator: walkthroughCoordinator)
149
            self?.dispatchApplication()
150
            self?.tabBarViewController.selectedIndex = 0
151 152
        }).disposed(by: self.disposeBag)
    }
153 154 155 156 157 158 159 160

    /// Prepares the main interface, should only be executed once
    private func prepareMainInterface() {
        guard self.mainInterfaceReady == false else {
            return
        }

        let conversationsCoordinator = ConversationsCoordinator(with: self.injectionBag)
161
        conversationsCoordinator.parentCoordinator = self
162
        let contactRequestsCoordinator = ContactRequestsCoordinator(with: self.injectionBag)
163
        contactRequestsCoordinator.parentCoordinator = self
164
        let meCoordinator = MeCoordinator(with: self.injectionBag)
165
        meCoordinator.parentCoordinator = self
Quentin Muret's avatar
Quentin Muret committed
166
        self.tabBarViewController.tabBar.tintColor = UIColor.jamiMain
167
        self.tabBarViewController.view.backgroundColor = UIColor.white
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

        self.tabBarViewController.viewControllers = [conversationsCoordinator.rootViewController,
                                                     contactRequestsCoordinator.rootViewController,
                                                     meCoordinator.rootViewController]

        self.addChildCoordinator(childCoordinator: conversationsCoordinator)
        self.addChildCoordinator(childCoordinator: contactRequestsCoordinator)
        self.addChildCoordinator(childCoordinator: meCoordinator)

        conversationsCoordinator.start()
        contactRequestsCoordinator.start()
        meCoordinator.start()

        self.mainInterfaceReady = true
    }

    /// Presents the main interface
    private func showMainInterface () {
        self.navigationController.setViewControllers([self.tabBarViewController], animated: true)
    }
188 189 190 191 192 193 194

    func openConversation (participantID: String) {
        self.tabBarViewController.selectedIndex = 0
        if let conversationCoordinator = self.childCoordinators[0] as? ConversationsCoordinator {
            conversationCoordinator.puchConversation(participantId: participantID)
        }
    }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
195 196 197 198 199 200 201 202 203 204 205 206 207

    func startCall(participant: String, name: String, isVideo: Bool) {
        for child in self.childCoordinators {
            if let childCoordinattor = child as? ConversationsCoordinator {
                if isVideo {
                    childCoordinattor.stateSubject
                        .onNext(ConversationState
                            .startCall(contactRingId: participant, userName: name))
                    return
                }
                childCoordinattor.stateSubject
                    .onNext(ConversationState
                        .startAudioCall(contactRingId: participant, userName: name))
208
                return
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
209 210 211
            }
        }
    }
212
}