Huge refactor of AppDelegate and authentication routing (#2238)

* refactor with new app controller, replace root nav mgr

* finish refactoring out root nav mgr

* remove root mgr
This commit is contained in:
Ryan Nystrom
2018-10-07 15:59:18 -04:00
committed by GitHub
parent a5ed67a924
commit e2b2bc9d47
17 changed files with 327 additions and 367 deletions

View File

@@ -14,7 +14,11 @@ import GitHubSession
private let loginURL = URL(string: "http://github.com/login/oauth/authorize?client_id=\(Secrets.GitHub.clientId)&scope=user+repo+notifications")!
private let callbackURLScheme = "freetime://"
final class LoginSplashViewController: UIViewController, GitHubSessionListener {
protocol LoginSplashViewControllerDelegate: class {
func finishLogin(token: String, authMethod: GitHubUserSession.AuthMethod, username: String)
}
final class LoginSplashViewController: UIViewController {
enum State {
case idle
@@ -22,12 +26,11 @@ final class LoginSplashViewController: UIViewController, GitHubSessionListener {
}
private var client: Client!
private var sessionManager: GitHubSessionManager!
@IBOutlet weak var splashView: SplashView!
@IBOutlet weak var signInButton: UIButton!
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
private weak var safariController: SFSafariViewController?
private weak var delegate: LoginSplashViewControllerDelegate?
@available(iOS 11.0, *)
private var authSession: SFAuthenticationSession? {
@@ -60,7 +63,6 @@ final class LoginSplashViewController: UIViewController, GitHubSessionListener {
override func viewDidLoad() {
super.viewDidLoad()
state = .idle
sessionManager.addListener(listener: self)
signInButton.layer.cornerRadius = Styles.Sizes.cardCornerRadius
}
@@ -72,9 +74,15 @@ final class LoginSplashViewController: UIViewController, GitHubSessionListener {
// MARK: Public API
func config(client: Client, sessionManager: GitHubSessionManager) {
self.client = client
self.sessionManager = sessionManager
static func make(client: Client, delegate: LoginSplashViewControllerDelegate) -> LoginSplashViewController? {
let controller = UIStoryboard(
name: "OauthLogin",
bundle: Bundle(for: AppDelegate.self))
.instantiateInitialViewController() as? LoginSplashViewController
controller?.client = client
controller?.delegate = delegate
controller?.modalPresentationStyle = .formSheet
return controller
}
// MARK: Private API
@@ -88,7 +96,22 @@ final class LoginSplashViewController: UIViewController, GitHubSessionListener {
}
return
}
self?.sessionManager.receivedCodeRedirect(url: callbackUrl)
guard let items = URLComponents(url: callbackUrl, resolvingAgainstBaseURL: false)?.queryItems,
let index = items.index(where: { $0.name == "code" }),
let code = items[index].value
else { return }
self?.state = .fetchingToken
self?.client.requestAccessToken(code: code) { [weak self] result in
switch result {
case .error:
self?.handleError()
case .success(let user):
self?.delegate?.finishLogin(token: user.token, authMethod: .oauth, username: user.username)
}
}
})
self.authSession?.start()
}
@@ -117,7 +140,7 @@ final class LoginSplashViewController: UIViewController, GitHubSessionListener {
case .failure:
self?.handleError()
case .success(let user):
self?.finishLogin(token: token, authMethod: .pat, username: user.data.login)
self?.delegate?.finishLogin(token: token, authMethod: .pat, username: user.data.login)
}
}
})
@@ -138,34 +161,8 @@ final class LoginSplashViewController: UIViewController, GitHubSessionListener {
present(alert, animated: trueUnlessReduceMotionEnabled)
}
private func finishLogin(token: String, authMethod: GitHubUserSession.AuthMethod, username: String) {
sessionManager.focus(
GitHubUserSession(token: token, authMethod: authMethod, username: username),
dismiss: true
)
}
private func setupSplashView() {
splashView.configureView()
}
// MARK: GitHubSessionListener
func didReceiveRedirect(manager: GitHubSessionManager, code: String) {
safariController?.dismiss(animated: trueUnlessReduceMotionEnabled)
state = .fetchingToken
client.requestAccessToken(code: code) { [weak self] result in
switch result {
case .error:
self?.handleError()
case .success(let user):
self?.finishLogin(token: user.token, authMethod: .oauth, username: user.username)
}
}
}
func didFocus(manager: GitHubSessionManager, userSession: GitHubUserSession, dismiss: Bool) {}
func didLogout(manager: GitHubSessionManager) {}
}

View File

@@ -116,7 +116,6 @@ final class SettingsAccountsViewController: UITableViewController, GitHubSession
tableView.reloadData()
}
func didReceiveRedirect(manager: GitHubSessionManager, code: String) {}
func didLogout(manager: GitHubSessionManager) {}
}

View File

@@ -17,8 +17,6 @@ NewIssueTableViewControllerDelegate {
// must be injected
var sessionManager: GitHubSessionManager!
weak var rootNavigationManager: RootNavigationManager?
var client: GithubClient!
@IBOutlet weak var versionLabel: UILabel!
@@ -158,7 +156,7 @@ NewIssueTableViewControllerDelegate {
func onReportBug() {
guard let viewController = NewIssueTableViewController.create(
client: newGithubClient(userSession: sessionManager.focusedUserSession),
client: GithubClient(userSession: sessionManager.focusedUserSession),
owner: "GitHawkApp",
repo: "GitHawk",
signature: .bugReport

View File

@@ -1,28 +0,0 @@
//
// Alamofire+GithubAPI.swift
// Freetime
//
// Created by Ryan Nystrom on 5/17/17.
// Copyright © 2017 Ryan Nystrom. All rights reserved.
//
import Foundation
import Alamofire
import Apollo
import GitHubSession
import GitHubAPI
func newGithubClient(
userSession: GitHubUserSession? = nil
) -> GithubClient {
let networkingConfigs = userSession?.networkingConfigs
let config = ConfiguredNetworkers(
token: networkingConfigs?.token,
useOauth: networkingConfigs?.useOauth
)
return GithubClient(
apollo: config.apollo,
networker: config.alamofire,
userSession: userSession
)
}

View File

@@ -17,28 +17,12 @@ import GitHubSession
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
private var showingLogin = false
private let flexController = FlexController()
private let sessionManager = GitHubSessionManager()
private var watchAppSync: WatchAppUserSessionSync?
private lazy var rootNavigationManager: RootNavigationManager = {
return RootNavigationManager(
sessionManager: self.sessionManager,
rootViewController: self.window?.rootViewController as! UISplitViewController
)
}()
private let appController = AppController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
sessionManager.addListener(listener: self)
let focusedSession = sessionManager.focusedUserSession
watchAppSync = WatchAppUserSessionSync(userSession: focusedSession)
watchAppSync?.start()
// initialize a webview at the start so webview startup later on isn't so slow
_ = UIWebView()
appController.appDidFinishLaunching(with: window)
// setup fabric
Fabric.with([Crashlytics.self])
@@ -49,10 +33,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// setup FLEX
flexController.configureWindow(window)
// setup root VCs
window?.backgroundColor = Styles.Colors.background
rootNavigationManager.resetRootViewController(userSession: focusedSession)
// use Alamofire status bar network activity helper
NetworkActivityIndicatorManager.shared.isEnabled = true
@@ -73,42 +53,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
completionHandler(false)
return
}
completionHandler(ShortcutHandler.handle(
route: route,
sessionManager: sessionManager,
navigationManager: rootNavigationManager))
completionHandler(appController.handle(route: route))
}
func applicationDidBecomeActive(_ application: UIApplication) {
if showingLogin == false && sessionManager.focusedUserSession == nil {
showingLogin = true
rootNavigationManager.showLogin(animated: false)
}
}
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if let sourceApp = options[.sourceApplication],
String(describing: sourceApp) == "com.apple.SafariViewService" {
sessionManager.receivedCodeRedirect(url: url)
return true
}
return false
appController.appDidBecomeActive()
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
rootNavigationManager.client?.badge.fetch(application: application, handler: completionHandler)
appController.performFetch(application: application, with: completionHandler)
}
}
extension AppDelegate: GitHubSessionListener {
// configure 3d touch shortcut handling
func didFocus(manager: GitHubSessionManager, userSession: GitHubUserSession, dismiss: Bool) {
ShortcutHandler.configure(application: UIApplication.shared, sessionManager: sessionManager)
watchAppSync?.sync(userSession: userSession)
}
func didReceiveRedirect(manager: GitHubSessionManager, code: String) {}
func didLogout(manager: GitHubSessionManager) {}
}

View File

@@ -0,0 +1,142 @@
//
// AppController.swift
// Freetime
//
// Created by Ryan Nystrom on 10/6/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//
import UIKit
import GitHubSession
import GitHubAPI
final class AppController: LoginSplashViewControllerDelegate, GitHubSessionListener {
private var splitViewController: AppSplitViewController!
private let sessionManager = GitHubSessionManager()
private weak var loginViewController: LoginSplashViewController?
private var appClient: GithubClient?
private var settingsNavigationController: UINavigationController?
private var watchAppSync: WatchAppUserSessionSync?
init() {
sessionManager.addListener(listener: self)
}
func appDidFinishLaunching(with window: UIWindow?) {
guard let controller = window?.rootViewController as? AppSplitViewController else {
fatalError("App must be setup with a split view controller")
}
splitViewController = controller
if let focused = sessionManager.focusedUserSession {
resetViewControllers(userSession: focused)
resetWatchSync(userSession: focused)
}
}
func appDidBecomeActive() {
// no need to login if there's a user session
guard sessionManager.focusedUserSession == nil
// avoid stacking logins
&& loginViewController == nil
else { return }
showLogin(animated: false)
}
func performFetch(
application: UIApplication,
with completion: @escaping (UIBackgroundFetchResult) -> Void
) {
appClient?.badge.fetch(application: application, handler: completion)
}
func handle(route: Route) -> Bool {
switch route {
case .tab(let tab):
splitViewController.select(tabAt: tab.rawValue)
return true
case .switchAccount(let sessionIndex):
if let index = sessionIndex {
let session = sessionManager.userSessions[index]
sessionManager.focus(session, dismiss: false)
}
return true
}
}
private func resetWatchSync(userSession: GitHubUserSession) {
watchAppSync = WatchAppUserSessionSync(userSession: userSession)
watchAppSync?.start()
}
private func resetViewControllers(userSession: GitHubUserSession) {
let appClient = GithubClient(userSession: userSession)
self.appClient = appClient
settingsNavigationController = settingsNavigationController
?? newSettingsRootViewController(sessionManager: sessionManager)
// keep settings up to date with the most recent client
if let settings = settingsNavigationController?.viewControllers.first as? SettingsViewController {
settings.client = appClient
}
splitViewController.reset(viewControllers: [
newNotificationsRootViewController(client: appClient),
newSearchRootViewController(client: appClient),
newBookmarksRootViewController(client: appClient),
settingsNavigationController ?? UINavigationController() // satisfy compiler
])
}
private func showLogin(animated: Bool) {
guard let controller = LoginSplashViewController.make(
client: Client.make(),
delegate: self
)
else { return }
loginViewController = controller
let present: () -> Void = {
self.splitViewController.present(controller, animated: animated)
}
if let presented = splitViewController.presentedViewController {
presented.dismiss(animated: animated, completion: present)
} else {
present()
}
// wipe underlying VCs clean. important for ipad (modal)
splitViewController.resetEmpty()
}
// MARK: LoginSplashViewControllerDelegate
func finishLogin(token: String, authMethod: GitHubUserSession.AuthMethod, username: String) {
sessionManager.focus(
GitHubUserSession(token: token, authMethod: authMethod, username: username),
dismiss: true
)
}
// MARK: GitHubSessionListener
func didFocus(manager: GitHubSessionManager, userSession: GitHubUserSession, dismiss: Bool) {
resetViewControllers(userSession: userSession)
if dismiss {
splitViewController.presentedViewController?.dismiss(animated: trueUnlessReduceMotionEnabled)
}
ShortcutHandler.configure(sessionUsernames: manager.userSessions.compactMap { $0.username })
if let watch = watchAppSync {
watch.sync(userSession: userSession)
} else {
resetWatchSync(userSession: userSession)
}
}
func didLogout(manager: GitHubSessionManager) {
showLogin(animated: trueUnlessReduceMotionEnabled)
}
}

View File

@@ -0,0 +1,46 @@
//
// AppSplitViewController.swift
// Freetime
//
// Created by Ryan Nystrom on 10/6/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//
import UIKit
final class AppSplitViewController: UISplitViewController {
private let tabDelegate = TabBarControllerDelegate()
private let appSplitViewDelegate = SplitViewControllerDelegate()
override func viewDidLoad() {
super.viewDidLoad()
masterTabBarController?.delegate = tabDelegate
delegate = appSplitViewDelegate
preferredDisplayMode = .allVisible
}
private var detailNavigationController: UINavigationController? {
return viewControllers.last as? UINavigationController
}
private var masterTabBarController: UITabBarController? {
return viewControllers.first as? UITabBarController
}
func select(tabAt index: Int) {
masterTabBarController?.selectedIndex = index
}
func resetEmpty() {
let controller = UIViewController()
controller.view.backgroundColor = Styles.Colors.background
reset(viewControllers: [UINavigationController(rootViewController: controller)])
}
func reset(viewControllers: [UIViewController]) {
masterTabBarController?.viewControllers = viewControllers
detailNavigationController?.viewControllers = [SplitPlaceholderViewController()]
}
}

View File

@@ -0,0 +1,28 @@
//
// Client+GithubUserSession.swift
// Freetime
//
// Created by Ryan Nystrom on 10/6/18.
// Copyright © 2018 Ryan Nystrom. All rights reserved.
//
import Foundation
import GitHubSession
import GitHubAPI
extension Client {
static func make(userSession: GitHubUserSession? = nil) -> Client {
let networkingConfigs = userSession?.networkingConfigs
let config = ConfiguredNetworkers(
token: networkingConfigs?.token,
useOauth: networkingConfigs?.useOauth
)
return Client(
httpPerformer: config.alamofire,
apollo: config.apollo,
token: userSession?.token
)
}
}

View File

@@ -21,14 +21,10 @@ struct GithubClient {
let client: Client
let badge: BadgeNotifications
init(
apollo: ApolloClient,
networker: Alamofire.SessionManager,
userSession: GitHubUserSession? = nil
) {
init(userSession: GitHubUserSession? = nil) {
self.userSession = userSession
self.client = Client(httpPerformer: networker, apollo: apollo, token: userSession?.token)
self.client = Client.make(userSession: userSession)
self.badge = BadgeNotifications(client: self.client)
if let token = userSession?.token {

View File

@@ -1,169 +0,0 @@
//
// RootNavigationManager.swift
// Freetime
//
// Created by Ryan Nystrom on 5/17/17.
// Copyright © 2017 Ryan Nystrom. All rights reserved.
//
import UIKit
import GitHubAPI
import GitHubSession
final class RootNavigationManager: GitHubSessionListener {
private let sessionManager: GitHubSessionManager
private let splitDelegate = SplitViewControllerDelegate()
private let tabDelegate = TabBarControllerDelegate()
// weak refs to avoid cycles
weak private var rootViewController: UISplitViewController?
// keep alive between switching accounts
private var settingsRootViewController: UIViewController?
private(set) var client: GithubClient?
init(
sessionManager: GitHubSessionManager,
rootViewController: UISplitViewController
) {
self.sessionManager = sessionManager
self.rootViewController = rootViewController
rootViewController.delegate = splitDelegate
rootViewController.preferredDisplayMode = .allVisible
sessionManager.addListener(listener: self)
self.tabBarController?.tabBar.tintColor = Styles.Colors.Blue.medium.color
self.tabBarController?.tabBar.unselectedItemTintColor = Styles.Colors.Gray.light.color
self.tabBarController?.delegate = self.tabDelegate
}
// MARK: Public API
public func showLogin(animated: Bool = false) {
guard let root = rootViewController else { return }
let login = newLoginViewController()
login.modalPresentationStyle = .formSheet
let block: () -> Void = { root.present(login, animated: animated) }
if let presented = root.presentedViewController {
presented.dismiss(animated: animated, completion: block)
} else {
block()
}
self.tabBarController?.selectedIndex = 0
}
public func resetRootViewController(userSession: GitHubUserSession?) {
guard let userSession = userSession else { return }
let client = newGithubClient(userSession: userSession)
self.client = client
fetchUsernameForMigrationIfNecessary(client: client, userSession: userSession, sessionManager: sessionManager)
// rebuild the settings VC if it doesn't exist
settingsRootViewController = settingsRootViewController ?? newSettingsRootViewController(
sessionManager: sessionManager,
client: client,
rootNavigationManager: self
)
tabBarController?.viewControllers = [
newNotificationsRootViewController(client: client),
newSearchRootViewController(client: client),
newBookmarksRootViewController(client: client),
settingsRootViewController ?? UIViewController() // simply satisfying compiler
]
}
public func pushLoginViewController(nav: UINavigationController) {
let login = newLoginViewController()
nav.pushViewController(login, animated: trueUnlessReduceMotionEnabled)
}
@discardableResult
public func selectViewController(atIndex index: Int) -> UIViewController? {
tabBarController?.selectedIndex = index
return tabBarController?.selectedViewController
}
@discardableResult
public func selectViewController(atTab tab: TabBarController.Tab) -> UIViewController? {
tabBarController?.showTab(tab)
return tabBarController?.selectedViewController
}
// MARK: GitHubSessionListener
func didFocus(manager: GitHubSessionManager, userSession: GitHubUserSession, dismiss: Bool) {
resetRootViewController(userSession: userSession)
if dismiss {
rootViewController?.presentedViewController?.dismiss(animated: trueUnlessReduceMotionEnabled)
}
}
func didLogout(manager: GitHubSessionManager) {
settingsRootViewController = nil
for vc in tabBarController?.viewControllers ?? [] {
if let nav = vc as? UINavigationController {
nav.viewControllers = [SplitPlaceholderViewController()]
}
}
detailNavigationController?.viewControllers = [SplitPlaceholderViewController()]
showLogin(animated: trueUnlessReduceMotionEnabled)
}
func didReceiveRedirect(manager: GitHubSessionManager, code: String) {}
// MARK: Private API
private func fetchUsernameForMigrationIfNecessary(
client: GithubClient,
userSession: GitHubUserSession,
sessionManager: GitHubSessionManager
) {
// only required when there is no username
guard userSession.username == nil else { return }
client.client.send(V3VerifyPersonalAccessTokenRequest(token: userSession.token)) { result in
switch result {
case .success(let user):
userSession.username = user.data.login
// user session ref is same session that manager should be using
// update w/ mutated session
sessionManager.save()
case .failure: break
}
}
}
private var detailNavigationController: UINavigationController? {
return rootViewController?.viewControllers.last as? UINavigationController
}
private var tabBarController: TabBarController? {
return rootViewController?.viewControllers.first as? TabBarController
}
private func newLoginViewController() -> UIViewController {
let controller = UIStoryboard(
name: "OauthLogin",
bundle: Bundle(for: AppDelegate.self))
.instantiateInitialViewController() as! LoginSplashViewController
controller.config(
client: newGithubClient().client,
sessionManager: sessionManager
)
return controller
}
}

View File

@@ -17,29 +17,11 @@ struct ShortcutHandler {
case switchAccount
}
static func configure(application: UIApplication, sessionManager: GitHubSessionManager) {
application.shortcutItems = generateItems(sessionManager: sessionManager)
static func configure(sessionUsernames: [String]) {
UIApplication.shared.shortcutItems = generateItems(sessionUsernames: sessionUsernames)
}
static func handle(
route: Route,
sessionManager: GitHubSessionManager,
navigationManager: RootNavigationManager
) -> Bool {
switch route {
case .tab(let tab):
navigationManager.selectViewController(atTab: tab)
return true
case .switchAccount(let sessionIndex):
if let index = sessionIndex {
let session = sessionManager.userSessions[index]
sessionManager.focus(session, dismiss: false)
}
return true
}
}
private static func generateItems(sessionManager: GitHubSessionManager) -> [UIApplicationShortcutItem] {
private static func generateItems(sessionUsernames: [String]) -> [UIApplicationShortcutItem] {
var items: [UIApplicationShortcutItem] = []
// Search
@@ -59,19 +41,17 @@ struct ShortcutHandler {
items.append(bookmarkItem)
// Switchuser
if sessionManager.userSessions.count > 1 {
let userSession = sessionManager.userSessions[1]
if let username = userSession.username {
let userIcon = UIApplicationShortcutIcon(templateImageName: "organization")
let userItem = UIApplicationShortcutItem(
type: Items.switchAccount.rawValue,
localizedTitle: NSLocalizedString("Switch Account", comment: ""),
localizedSubtitle: username,
icon: userIcon,
userInfo: ["sessionIndex": 1]
)
items.append(userItem)
}
if sessionUsernames.count > 1 {
let username = sessionUsernames[1]
let userIcon = UIApplicationShortcutIcon(templateImageName: "organization")
let userItem = UIApplicationShortcutItem(
type: Items.switchAccount.rawValue,
localizedTitle: NSLocalizedString("Switch Account", comment: ""),
localizedSubtitle: username,
icon: userIcon,
userInfo: ["sessionIndex": 1]
)
items.append(userItem)
}
return items

View File

@@ -10,24 +10,19 @@ import UIKit
import GitHubSession
func newSettingsRootViewController(
sessionManager: GitHubSessionManager,
client: GithubClient,
rootNavigationManager: RootNavigationManager
) -> UIViewController {
guard let controller = UIStoryboard(name: "Settings", bundle: nil).instantiateInitialViewController()
sessionManager: GitHubSessionManager
) -> UINavigationController {
guard let nav = UIStoryboard(name: "Settings", bundle: nil).instantiateInitialViewController() as? UINavigationController
else { fatalError("Could not unpack settings storyboard") }
if let nav = controller as? UINavigationController,
let first = nav.viewControllers.first as? SettingsViewController {
first.client = client
if let first = nav.viewControllers.first as? SettingsViewController {
first.sessionManager = sessionManager
first.rootNavigationManager = rootNavigationManager
nav.tabBarItem.title = NSLocalizedString("Settings", comment: "")
nav.tabBarItem.image = UIImage(named: "tab-gear")?.withRenderingMode(.alwaysOriginal)
nav.tabBarItem.selectedImage = UIImage(named: "tab-gear-selected")?.withRenderingMode(.alwaysOriginal)
}
return controller
return nav
}
func newNotificationsRootViewController(client: GithubClient) -> UIViewController {

View File

@@ -22,6 +22,9 @@
290744BC1F268D8300FD9E48 /* UserAutocomplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290744BB1F268D8300FD9E48 /* UserAutocomplete.swift */; };
290744BE1F268F8700FD9E48 /* UserAutocomplete+GraphQL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290744BD1F268F8700FD9E48 /* UserAutocomplete+GraphQL.swift */; };
2908C5891F6F3EB00071C39D /* IssueLocalReaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2908C5881F6F3EB00071C39D /* IssueLocalReaction.swift */; };
290CA7642169799600DE04F8 /* AppController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290CA7632169799600DE04F8 /* AppController.swift */; };
290CA76621697A7900DE04F8 /* AppSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290CA76521697A7900DE04F8 /* AppSplitViewController.swift */; };
290CA768216984F000DE04F8 /* Client+GithubUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290CA767216984F000DE04F8 /* Client+GithubUserSession.swift */; };
290D2A3D1F044CB20082E6CC /* UIViewController+SmartDeselection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290D2A3C1F044CB20082E6CC /* UIViewController+SmartDeselection.swift */; };
290D2A421F04D3470082E6CC /* IssueStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290D2A411F04D3470082E6CC /* IssueStatus.swift */; };
290EF56A1F06A821006A2160 /* Notification+NotificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290EF5691F06A7E1006A2160 /* Notification+NotificationViewModel.swift */; };
@@ -117,8 +120,6 @@
2930F2731F8A27750082BA26 /* WidthCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2930F2721F8A27750082BA26 /* WidthCache.swift */; };
29316DB51ECC7DEB007CAE3F /* ButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29316DB41ECC7DEB007CAE3F /* ButtonCell.swift */; };
29316DBF1ECC95DB007CAE3F /* RootViewControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29316DBE1ECC95DB007CAE3F /* RootViewControllers.swift */; };
29316DC31ECC981D007CAE3F /* RootNavigationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29316DC21ECC981D007CAE3F /* RootNavigationManager.swift */; };
29316DC51ECC9841007CAE3F /* Alamofire+GithubAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29316DC41ECC9841007CAE3F /* Alamofire+GithubAPI.swift */; };
293189281F5391F700EF0911 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293189271F5391F700EF0911 /* Result.swift */; };
2931892B1F5397E400EF0911 /* IssueMilestoneCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931892A1F5397E400EF0911 /* IssueMilestoneCell.swift */; };
2931892F1F539C0E00EF0911 /* IssueMilestoneSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931892E1F539C0E00EF0911 /* IssueMilestoneSectionController.swift */; };
@@ -544,6 +545,9 @@
290744BB1F268D8300FD9E48 /* UserAutocomplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAutocomplete.swift; sourceTree = "<group>"; };
290744BD1F268F8700FD9E48 /* UserAutocomplete+GraphQL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserAutocomplete+GraphQL.swift"; sourceTree = "<group>"; };
2908C5881F6F3EB00071C39D /* IssueLocalReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueLocalReaction.swift; sourceTree = "<group>"; };
290CA7632169799600DE04F8 /* AppController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppController.swift; sourceTree = "<group>"; };
290CA76521697A7900DE04F8 /* AppSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSplitViewController.swift; sourceTree = "<group>"; };
290CA767216984F000DE04F8 /* Client+GithubUserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Client+GithubUserSession.swift"; sourceTree = "<group>"; };
290D2A3C1F044CB20082E6CC /* UIViewController+SmartDeselection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+SmartDeselection.swift"; sourceTree = "<group>"; };
290D2A411F04D3470082E6CC /* IssueStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueStatus.swift; sourceTree = "<group>"; };
290EF5691F06A7E1006A2160 /* Notification+NotificationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+NotificationViewModel.swift"; sourceTree = "<group>"; };
@@ -640,8 +644,6 @@
2930F2721F8A27750082BA26 /* WidthCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidthCache.swift; sourceTree = "<group>"; };
29316DB41ECC7DEB007CAE3F /* ButtonCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonCell.swift; sourceTree = "<group>"; };
29316DBE1ECC95DB007CAE3F /* RootViewControllers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootViewControllers.swift; sourceTree = "<group>"; };
29316DC21ECC981D007CAE3F /* RootNavigationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootNavigationManager.swift; sourceTree = "<group>"; };
29316DC41ECC9841007CAE3F /* Alamofire+GithubAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Alamofire+GithubAPI.swift"; sourceTree = "<group>"; };
293189271F5391F700EF0911 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
2931892A1F5397E400EF0911 /* IssueMilestoneCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueMilestoneCell.swift; sourceTree = "<group>"; };
2931892E1F539C0E00EF0911 /* IssueMilestoneSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueMilestoneSectionController.swift; sourceTree = "<group>"; };
@@ -1080,6 +1082,15 @@
path = Autocomplete;
sourceTree = "<group>";
};
290CA7622169798B00DE04F8 /* AppRouter */ = {
isa = PBXGroup;
children = (
290CA7632169799600DE04F8 /* AppController.swift */,
290CA76521697A7900DE04F8 /* AppSplitViewController.swift */,
);
path = AppRouter;
sourceTree = "<group>";
};
291929431F3EAAAF0012067B /* Files */ = {
isa = PBXGroup;
children = (
@@ -1645,10 +1656,11 @@
297AE8651EC0D5C100B44A1F /* Systems */ = {
isa = PBXGroup;
children = (
29316DC41ECC9841007CAE3F /* Alamofire+GithubAPI.swift */,
297AE8671EC0D5C200B44A1F /* AppDelegate.swift */,
290CA7622169798B00DE04F8 /* AppRouter */,
290744B01F250A1D00FD9E48 /* Autocomplete */,
291929661F3FF9C50012067B /* BadgeNotifications.swift */,
290CA767216984F000DE04F8 /* Client+GithubUserSession.swift */,
DC78570F1F97F546009BADDA /* Debouncer.swift */,
29C167791ECA14F700439D62 /* Feed.swift */,
54AD5E8D1F24D953004A4BD6 /* FeedSelectionProviding.swift */,
@@ -1666,7 +1678,6 @@
292CD3D31F0DC12100D3D57B /* PhotoViewHandler.swift */,
2980033A1F51E82400BE90F4 /* Rating */,
293189271F5391F700EF0911 /* Result.swift */,
29316DC21ECC981D007CAE3F /* RootNavigationManager.swift */,
65A3152A2044376D0074E3B6 /* Route.swift */,
294A3D751FB29843000E81A4 /* ScrollViewKeyboardAdjuster.swift */,
9870B9021FC73EE70009719C /* Secrets.swift */,
@@ -2712,7 +2723,6 @@
D8BAD0601FDA0A1A00C41071 /* LabelListCell.swift in Sources */,
290744A91F24D2DA00FD9E48 /* AddCommentClient.swift in Sources */,
29DAA7AD20202A320029277A /* PullRequestReviewReplyCell.swift in Sources */,
29316DC51ECC9841007CAE3F /* Alamofire+GithubAPI.swift in Sources */,
75A0ACF51F79A82D0062D99A /* AlertAction.swift in Sources */,
75468F7A1F7AFBC800F2BC19 /* AlertActionBuilder.swift in Sources */,
292FCB2C1EE054900026635E /* API.swift in Sources */,
@@ -2767,6 +2777,7 @@
296B4E311F7C805600C16887 /* GraphQLIDDecode.swift in Sources */,
294B11241F7B37D300E04F2D /* ImageCellHeightCache.swift in Sources */,
294563EC1EE5012100DBCD35 /* Issue+IssueType.swift in Sources */,
290CA7642169799600DE04F8 /* AppController.swift in Sources */,
29AF1E821F8AAB2B0008A0EF /* EditCommentViewController.swift in Sources */,
297403D71F1851C000ABA95A /* IssueAssigneeAvatarCell.swift in Sources */,
297403D11F184F8D00ABA95A /* IssueAssigneesModel.swift in Sources */,
@@ -2815,6 +2826,7 @@
2974069D1F0EDEAD003A6BFB /* IssueCommentTableCell.swift in Sources */,
29764C141FDC4DB60095FF95 /* SettingsLabel.swift in Sources */,
2974069F1F0EDED3003A6BFB /* IssueCommentTableCollectionCell.swift in Sources */,
290CA768216984F000DE04F8 /* Client+GithubUserSession.swift in Sources */,
2974069B1F0EDC7C003A6BFB /* IssueCommentTableModel.swift in Sources */,
292FCB0A1EDFCC510026635E /* IssueCommentTextCell.swift in Sources */,
29BE40D32070786400A79C86 /* CMarkParsing.swift in Sources */,
@@ -2963,6 +2975,7 @@
BDB6AA68215FBC35009BB73C /* RepositoryBranchesSectionController.swift in Sources */,
BDB6AA6B215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift in Sources */,
292CD3D41F0DC12100D3D57B /* PhotoViewHandler.swift in Sources */,
290CA76621697A7900DE04F8 /* AppSplitViewController.swift in Sources */,
294563EE1EE5012900DBCD35 /* PullRequest+IssueType.swift in Sources */,
298003401F51E93B00BE90F4 /* RatingCell.swift in Sources */,
29351EA22079663800FF8C17 /* String+Shortlink.swift in Sources */,
@@ -3006,7 +3019,6 @@
2963A9341EE2118E0066509C /* ResponderButton.swift in Sources */,
BDB6AA69215FBC35009BB73C /* RepositoryBranchesViewController.swift in Sources */,
293189281F5391F700EF0911 /* Result.swift in Sources */,
29316DC31ECC981D007CAE3F /* RootNavigationManager.swift in Sources */,
295B51421FC26B8100C3993B /* PeopleCell.swift in Sources */,
29316DBF1ECC95DB007CAE3F /* RootViewControllers.swift in Sources */,
29DA1E791F5DEE8F0050C64B /* SearchLoadingView.swift in Sources */,

View File

@@ -23,7 +23,7 @@ class UrlPathComponents: XCTestCase {
add: true,
people: [])
let githubclient = newGithubClient()
let githubclient = GithubClient()
let promise = expectation(description: "completion handler invoked")
var requestResponse: GitHubAPI.Result<V3StatusCodeResponse<V3StatusCode200or201>>?
@@ -56,7 +56,7 @@ class UrlPathComponents: XCTestCase {
add: true,
people: [])
let githubclient = newGithubClient()
let githubclient = GithubClient()
let promise = expectation(description: "completion handler invoked")
var requestResponse: GitHubAPI.Result<V3StatusCodeResponse<V3StatusCode200or201>>?

View File

@@ -41,10 +41,16 @@
{
"size" : "44x44",
"idiom" : "watch",
"filename" : "Icon-HomeScreen-42mm-44x44@2x.png",
"scale" : "2x",
"role" : "longLook",
"subtype" : "42mm"
"role" : "appLauncher",
"subtype" : "40mm"
},
{
"size" : "50x50",
"idiom" : "watch",
"scale" : "2x",
"role" : "appLauncher",
"subtype" : "44mm"
},
{
"size" : "86x86",
@@ -62,11 +68,26 @@
"role" : "quickLook",
"subtype" : "42mm"
},
{
"size" : "108x108",
"idiom" : "watch",
"scale" : "2x",
"role" : "quickLook",
"subtype" : "44mm"
},
{
"size" : "1024x1024",
"idiom" : "watch-marketing",
"filename" : "Icon-AppStore-1024x1024.png",
"scale" : "1x"
},
{
"size" : "44x44",
"idiom" : "watch",
"filename" : "Icon-HomeScreen-42mm-44x44@2x.png",
"scale" : "2x",
"role" : "longLook",
"subtype" : "42mm"
}
],
"info" : {

View File

@@ -9,7 +9,6 @@
import Foundation
public protocol GitHubSessionListener: class {
func didReceiveRedirect(manager: GitHubSessionManager, code: String)
func didFocus(manager: GitHubSessionManager, userSession: GitHubUserSession, dismiss: Bool)
func didLogout(manager: GitHubSessionManager)
}
@@ -111,14 +110,4 @@ public class GitHubSessionManager: NSObject {
}
}
public func receivedCodeRedirect(url: URL) {
guard let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems,
let index = items.index(where: { $0.name == "code" }),
let code = items[index].value
else { return }
for wrapper in listeners {
wrapper.listener?.didReceiveRedirect(manager: self, code: code)
}
}
}

View File

@@ -1,16 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="EPN-4V-9Hj">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="EPN-4V-9Hj">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Notifications-->
<scene sceneID="5Pm-pl-ZQT">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="V24-0g-MmQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
<navigationController id="AKn-8S-pzd" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Notifications" image="inbox" id="KQ4-AC-rKh"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="crz-Uj-ajM">
@@ -25,7 +27,6 @@
<segue destination="icu-Mt-euN" kind="relationship" relationship="rootViewController" id="DU7-Bf-pgd"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="V24-0g-MmQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="268" y="536.58170914542734"/>
</scene>
@@ -92,10 +93,10 @@
</objects>
<point key="canvasLocation" x="167" y="1321"/>
</scene>
<!--Split View Controller-->
<!--App Split View Controller-->
<scene sceneID="0c8-No-DSu">
<objects>
<splitViewController id="EPN-4V-9Hj" sceneMemberID="viewController">
<splitViewController id="EPN-4V-9Hj" customClass="AppSplitViewController" customModule="Freetime" customModuleProvider="target" sceneMemberID="viewController">
<connections>
<segue destination="f5k-7G-hod" kind="relationship" relationship="masterViewController" id="aFN-Se-rpt"/>
<segue destination="D4E-Yy-PTN" kind="relationship" relationship="detailViewController" id="gsk-8r-3gM"/>