Refactor view controller creation/navigation into AppRouter

This commit is contained in:
Shadowfacts 2018-10-20 22:07:04 -04:00
parent 788e0500de
commit 54f142e3f9
19 changed files with 246 additions and 193 deletions

View File

@ -60,6 +60,7 @@
D621544821682A9D0003D87D /* TabsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D621544721682A9D0003D87D /* TabsTableViewController.swift */; }; D621544821682A9D0003D87D /* TabsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D621544721682A9D0003D87D /* TabsTableViewController.swift */; };
D621544B21682AD30003D87D /* TabTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D621544A21682AD30003D87D /* TabTableViewCell.swift */; }; D621544B21682AD30003D87D /* TabTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D621544A21682AD30003D87D /* TabTableViewCell.swift */; };
D621544D21682AD90003D87D /* TabTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D621544C21682AD90003D87D /* TabTableViewCell.xib */; }; D621544D21682AD90003D87D /* TabTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D621544C21682AD90003D87D /* TabTableViewCell.xib */; };
D627FF74217BBC9700CC0648 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF73217BBC9700CC0648 /* AppRouter.swift */; };
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; }; D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; }; D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; }; D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
@ -247,6 +248,7 @@
D621544721682A9D0003D87D /* TabsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewController.swift; sourceTree = "<group>"; }; D621544721682A9D0003D87D /* TabsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewController.swift; sourceTree = "<group>"; };
D621544A21682AD30003D87D /* TabTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabTableViewCell.swift; sourceTree = "<group>"; }; D621544A21682AD30003D87D /* TabTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabTableViewCell.swift; sourceTree = "<group>"; };
D621544C21682AD90003D87D /* TabTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TabTableViewCell.xib; sourceTree = "<group>"; }; D621544C21682AD90003D87D /* TabTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TabTableViewCell.xib; sourceTree = "<group>"; };
D627FF73217BBC9700CC0648 /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = "<group>"; };
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; }; D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; }; D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; }; D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
@ -743,6 +745,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */, D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
D627FF73217BBC9700CC0648 /* AppRouter.swift */,
D64D0AAC2128D88B005A6F37 /* LocalData.swift */, D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
04DACE8D212CC7CC009840C4 /* ImageCache.swift */, 04DACE8D212CC7CC009840C4 /* ImageCache.swift */,
D6028B9A2150811100F223B9 /* MastodonCache.swift */, D6028B9A2150811100F223B9 /* MastodonCache.swift */,
@ -1137,6 +1140,7 @@
D6C693CA2161253F007D6A6D /* SilentActionPermissionsTableViewController.swift in Sources */, D6C693CA2161253F007D6A6D /* SilentActionPermissionsTableViewController.swift in Sources */,
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */, D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */, D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */,
D627FF74217BBC9700CC0648 /* AppRouter.swift in Sources */,
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */, D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */, D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */, D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,

View File

@ -12,6 +12,7 @@ import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate { class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var window: UIWindow?
var router: AppRouter!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds) window = UIWindow(frame: UIScreen.main.bounds)
@ -66,7 +67,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
MastodonController.getOwnAccount() MastodonController.getOwnAccount()
MastodonController.getOwnInstance() MastodonController.getOwnInstance()
window!.rootViewController = MainTabBarViewController() let tabBarController = MainTabBarViewController()
window!.rootViewController = tabBarController
router = tabBarController.router
} }
func showOnboardingUI() { func showOnboardingUI() {

127
Tusker/AppRouter.swift Normal file
View File

@ -0,0 +1,127 @@
//
// AppRouter.swift
// Tusker
//
// Created by Shadowfacts on 10/20/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import SafariServices
class AppRouter {
let rootViewController: UIViewController
var currentViewController: UIViewController {
return getContentViewController(from: rootViewController)
}
init(root: UIViewController) {
self.rootViewController = root
}
private func getContentViewController(from vc: UIViewController) -> UIViewController {
if let vc = vc as? UITabBarController,
let selected = vc.selectedViewController {
return getContentViewController(from: selected)
} else if let vc = vc as? UINavigationController,
let top = vc.topViewController {
return getContentViewController(from: top)
} else {
return vc
}
}
func present(_ vc: UIViewController, animated: Bool) {
if currentViewController.presentingViewController != nil {
currentViewController.dismiss(animated: animated) {
self.present(vc, animated: animated)
}
}
currentViewController.present(vc, animated: animated)
}
func push(_ vc: UIViewController, animated: Bool) {
if currentViewController.presentingViewController != nil {
currentViewController.dismiss(animated: animated) {
self.push(vc, animated: animated)
}
}
guard let nav = currentViewController.navigationController else { fatalError("Can't push view controller while not in navigation controller") }
nav.pushViewController(vc, animated: animated)
}
func profile(for accountID: String) -> ProfileTableViewController {
return ProfileTableViewController(accountID: accountID, router: self)
}
func profile(for mention: Mention) -> ProfileTableViewController {
return ProfileTableViewController(accountID: mention.id, router: self)
}
func timeline(for timeline: Timeline) -> TimelineTableViewController {
return TimelineTableViewController(for: timeline, router: self)
}
func timeline(tag: Hashtag) -> TimelineTableViewController {
return timeline(for: .tag(hashtag: tag.name))
}
func safariViewController(for url: URL) -> SFSafariViewController {
return SFSafariViewController(url: url)
}
func conversation(for statusID: String) -> ConversationTableViewController {
return ConversationTableViewController(for: statusID, router: self)
}
func compose(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil) -> ComposeViewController {
return ComposeViewController(inReplyTo: inReplyToID, mentioningAcct: mentioningAcct, text: text, router: self)
}
func largeImage(_ image: UIImage, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, transitioningDelegate: UIViewControllerTransitioningDelegate?) -> LargeImageViewController {
let vc = LargeImageViewController(image: image, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, router: self)
vc.transitioningDelegate = transitioningDelegate
return vc
}
func moreOptions(forStatus statusID: String) -> UIAlertController {
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
if let url = status.url {
alert.addAction(UIAlertAction(title: "Open in Safari", style: .default, handler: { (_) in
let vc = SFSafariViewController(url: url)
self.present(vc, animated: true)
}))
alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { (_) in
UIPasteboard.general.url = url
}))
alert.addAction(UIAlertAction(title: "Share...", style: .default, handler: { (_) in
let vc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
self.present(vc, animated: true)
}))
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
return alert
}
func moreOptions(forURL url: URL) -> UIAlertController {
let alert = UIAlertController(title: nil, message: url.absoluteString, preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Open in Safari", style: .default, handler: { (_) in
let vc = SFSafariViewController(url: url)
self.present(vc, animated: true)
}))
alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { (_) in
UIPasteboard.general.url = url
}))
alert.addAction(UIAlertAction(title: "Share...", style: .default, handler: { (_) in
let vc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
self.present(vc, animated: true)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
return alert
}
}

View File

@ -7,14 +7,6 @@
// //
import UIKit import UIKit
import Pachyderm
import SafariServices
extension LargeImageViewControllerDelegate where Self: UIViewController {
func closeLargeImage() {
dismiss(animated: true)
}
}
extension UIViewController: UIViewControllerTransitioningDelegate { extension UIViewController: UIViewControllerTransitioningDelegate {
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {

View File

@ -12,6 +12,8 @@ import Intents
class ComposeViewController: UIViewController { class ComposeViewController: UIViewController {
let router: AppRouter
@IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var inReplyToContainerView: UIView! @IBOutlet weak var inReplyToContainerView: UIView!
@IBOutlet weak var inReplyToAvatarImageView: UIImageView! @IBOutlet weak var inReplyToAvatarImageView: UIImageView!
@ -51,11 +53,14 @@ class ComposeViewController: UIViewController {
var status: Status? var status: Status?
init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil) { init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil, router: AppRouter) {
super.init(nibName: "ComposeViewController", bundle: nil)
self.inReplyToID = inReplyToID self.inReplyToID = inReplyToID
self.mentioningAcct = mentioningAcct self.mentioningAcct = mentioningAcct
self.text = text self.text = text
self.router = router
super.init(nibName: "ComposeViewController", bundle: nil)
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelPressed)) navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelPressed))
} }

View File

@ -11,8 +11,9 @@ import Pachyderm
class ConversationTableViewController: UITableViewController { class ConversationTableViewController: UITableViewController {
var mainStatusID: String! let router: AppRouter
var mainStatusID: String!
var statusIDs: [String] = [] { var statusIDs: [String] = [] {
didSet { didSet {
DispatchQueue.main.async { DispatchQueue.main.async {
@ -21,9 +22,11 @@ class ConversationTableViewController: UITableViewController {
} }
} }
init(for mainStatusID: String) { init(for mainStatusID: String, router: AppRouter) {
super.init(style: .plain)
self.mainStatusID = mainStatusID self.mainStatusID = mainStatusID
self.router = router
super.init(style: .plain)
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
@ -74,7 +77,7 @@ class ConversationTableViewController: UITableViewController {
if let status = MastodonCache.status(for: mainStatusID), if let status = MastodonCache.status(for: mainStatusID),
let url = status.url { let url = status.url {
actions.append(UIPreviewAction(title: "Open in Safari", style: .default, handler: { (_, _) in actions.append(UIPreviewAction(title: "Open in Safari", style: .default, handler: { (_, _) in
let vc = self.viewController(forURL: url) let vc = self.router.safariViewController(for: url)
UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true) UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true)
})) }))
actions.append(UIPreviewAction(title: "Share", style: .default, handler: { (_, _) in actions.append(UIPreviewAction(title: "Share", style: .default, handler: { (_, _) in
@ -145,4 +148,3 @@ class ConversationTableViewController: UITableViewController {
} }
extension ConversationTableViewController: StatusTableViewCellDelegate {} extension ConversationTableViewController: StatusTableViewCellDelegate {}
extension ConversationTableViewController: LargeImageViewControllerDelegate {}

View File

@ -9,13 +9,9 @@
import UIKit import UIKit
import Photos import Photos
protocol LargeImageViewControllerDelegate {
func closeLargeImage()
}
class LargeImageViewController: UIViewController, UIScrollViewDelegate { class LargeImageViewController: UIViewController, UIScrollViewDelegate {
var delegate: LargeImageViewControllerDelegate? let router: AppRouter
var originFrame: CGRect? var originFrame: CGRect?
var originCornerRadius: CGFloat? var originCornerRadius: CGFloat?
@ -64,22 +60,14 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
return true return true
} }
init(image: UIImage, description: String?, sourceView: UIView, sourceViewController: UIViewController) { init(image: UIImage, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, router: AppRouter) {
super.init(nibName: "LargeImageViewController", bundle: nil) self.router = router
self.image = image self.image = image
self.imageDescription = description self.imageDescription = description
var frame = sourceView.convert(sourceView.bounds, to: sourceViewController.view) self.originFrame = sourceFrame
if let scrollView = sourceViewController.view as? UIScrollView { self.originCornerRadius = sourceCornerRadius
let scale = scrollView.zoomScale
let width = frame.width * scale super.init(nibName: "LargeImageViewController", bundle: nil)
let height = frame.height * scale
let x = frame.minX * scale - scrollView.contentOffset.x + scrollView.frame.minX
let y = frame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
frame = CGRect(x: x, y: y, width: width, height: height)
}
self.originFrame = frame
self.originCornerRadius = sourceView.layer.cornerRadius
self.transitioningDelegate = sourceViewController
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
@ -206,7 +194,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
} }
@IBAction func closeButtonPressed(_ sender: Any) { @IBAction func closeButtonPressed(_ sender: Any) {
delegate?.closeLargeImage() dismiss(animated: true)
} }
@IBAction func downloadPressed(_ sender: Any) { @IBAction func downloadPressed(_ sender: Any) {

View File

@ -10,6 +10,8 @@ import UIKit
class MainTabBarViewController: UITabBarController { class MainTabBarViewController: UITabBarController {
lazy var router = AppRouter(root: self)
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -31,20 +33,20 @@ class MainTabBarViewController: UITabBarController {
func createVC(for tab: Tab) -> UIViewController { func createVC(for tab: Tab) -> UIViewController {
switch tab { switch tab {
case .home: case .home:
return TimelineTableViewController(for: .home) return TimelineTableViewController(for: .home, router: router)
case .federated: case .federated:
return TimelineTableViewController(for: .public(local: false)) return TimelineTableViewController(for: .public(local: false), router: router)
case .local: case .local:
return TimelineTableViewController(for: .public(local: true)) return TimelineTableViewController(for: .public(local: true), router: router)
case .myProfile: case .myProfile:
let myProfile = ProfileTableViewController(accountID: nil) let myProfile = ProfileTableViewController(accountID: nil, router: router)
myProfile.title = "My Profile" myProfile.title = "My Profile"
MastodonController.getOwnAccount { (account) in MastodonController.getOwnAccount { (account) in
myProfile.accountID = account.id myProfile.accountID = account.id
} }
return myProfile return myProfile
case .notifications: case .notifications:
return embedInNavigationController(NotificationsTableViewController()) return NotificationsTableViewController(router: router)
case .preferences: case .preferences:
return PreferencesTableViewController.create() return PreferencesTableViewController.create()
} }

View File

@ -11,6 +11,8 @@ import Pachyderm
class NotificationsTableViewController: UITableViewController { class NotificationsTableViewController: UITableViewController {
let router: AppRouter
var notifications: [Pachyderm.Notification] = [] { var notifications: [Pachyderm.Notification] = [] {
didSet { didSet {
DispatchQueue.main.async { DispatchQueue.main.async {
@ -22,8 +24,11 @@ class NotificationsTableViewController: UITableViewController {
var newer: RequestRange? var newer: RequestRange?
var older: RequestRange? var older: RequestRange?
init() { init(router: AppRouter) {
self.router = router
super.init(style: .plain) super.init(style: .plain)
self.title = "Notifications" self.title = "Notifications"
self.refreshControl = UIRefreshControl() self.refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshNotifications(_:)), for: .valueChanged) refreshControl!.addTarget(self, action: #selector(refreshNotifications(_:)), for: .valueChanged)
@ -149,4 +154,3 @@ class NotificationsTableViewController: UITableViewController {
} }
extension NotificationsTableViewController: StatusTableViewCellDelegate {} extension NotificationsTableViewController: StatusTableViewCellDelegate {}
extension NotificationsTableViewController: LargeImageViewControllerDelegate {}

View File

@ -12,6 +12,8 @@ import SafariServices
class ProfileTableViewController: UITableViewController, PreferencesAdaptive { class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
let router: AppRouter
var accountID: String! { var accountID: String! {
didSet { didSet {
if shouldLoadOnAccountIDSet { if shouldLoadOnAccountIDSet {
@ -36,9 +38,12 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
var shouldLoadOnAccountIDSet = false var shouldLoadOnAccountIDSet = false
var loadingVC: LoadingViewController? = nil var loadingVC: LoadingViewController? = nil
init(accountID: String?) { init(accountID: String?, router: AppRouter) {
super.init(style: .plain)
self.accountID = accountID self.accountID = accountID
self.router = router
super.init(style: .plain)
self.refreshControl = UIRefreshControl() self.refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshStatuses(_:)), for: .valueChanged) refreshControl!.addTarget(self, action: #selector(refreshStatuses(_:)), for: .valueChanged)
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composePressed(_:))) navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composePressed(_:)))
@ -107,7 +112,7 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
var actions = [UIPreviewActionItem]() var actions = [UIPreviewActionItem]()
if let account = MastodonCache.account(for: accountID) { if let account = MastodonCache.account(for: accountID) {
actions.append(UIPreviewAction(title: "Open in Safari", style: .default, handler: { (_, _) in actions.append(UIPreviewAction(title: "Open in Safari", style: .default, handler: { (_, _) in
let vc = self.viewController(forURL: account.url) let vc = self.router.safariViewController(for: account.url)
UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true) UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true)
})) }))
actions.append(UIPreviewAction(title: "Share", style: .default, handler: { (_, _) in actions.append(UIPreviewAction(title: "Share", style: .default, handler: { (_, _) in
@ -115,7 +120,7 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true) UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true)
})) }))
actions.append(UIPreviewAction(title: "Send Message", style: .default, handler: { (_, _) in actions.append(UIPreviewAction(title: "Send Message", style: .default, handler: { (_, _) in
let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct)) let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, router: self.router))
UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true) UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true)
})) }))
} }
@ -148,7 +153,7 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
func sendMessageMentioning() { func sendMessageMentioning() {
guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") }
let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct)) let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, router: router))
present(vc, animated: true) present(vc, animated: true)
} }
@ -279,4 +284,3 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
} }
} }
} }
extension ProfileTableViewController: LargeImageViewControllerDelegate {}

View File

@ -11,6 +11,8 @@ import Pachyderm
class TimelineTableViewController: UITableViewController { class TimelineTableViewController: UITableViewController {
let router: AppRouter
lazy var favoriteActionImage: UIImage = UIGraphicsImageRenderer(size: CGSize(width: 30 * 137/131, height: 30)).image { _ in lazy var favoriteActionImage: UIImage = UIGraphicsImageRenderer(size: CGSize(width: 30 * 137/131, height: 30)).image { _ in
UIImage(named: "Favorite")!.draw(in: CGRect(x: 0, y: 0, width: 30 * 137/131, height: 30)) UIImage(named: "Favorite")!.draw(in: CGRect(x: 0, y: 0, width: 30 * 137/131, height: 30))
} }
@ -37,9 +39,11 @@ class TimelineTableViewController: UITableViewController {
var newer: RequestRange? var newer: RequestRange?
var older: RequestRange? var older: RequestRange?
init(for timeline: Timeline) { init(for timeline: Timeline, router: AppRouter) {
super.init(style: .plain)
self.timeline = timeline self.timeline = timeline
self.router = router
super.init(style: .plain)
switch timeline { switch timeline {
case .home: case .home:
@ -168,4 +172,3 @@ class TimelineTableViewController: UITableViewController {
} }
extension TimelineTableViewController: StatusTableViewCellDelegate {} extension TimelineTableViewController: StatusTableViewCellDelegate {}
extension TimelineTableViewController: LargeImageViewControllerDelegate {}

View File

@ -12,15 +12,9 @@ import Pachyderm
class UserActivityManager { class UserActivityManager {
// MARK: - Utils // MARK: - Utils
private static func presentModally(_ vc: UIViewController, animated: Bool, completion: (() -> Void)? = nil) { private static var router: AppRouter = {
UIApplication.shared.keyWindow!.rootViewController!.present(vc, animated: animated, completion: completion) return (UIApplication.shared.delegate as! AppDelegate).router
} }()
private static func presentNav(_ vc: UIViewController, animated: Bool) {
let tabBarController = UIApplication.shared.keyWindow!.rootViewController! as! UITabBarController
let navController = tabBarController.selectedViewController as! UINavigationController
navController.pushViewController(vc, animated: animated)
}
// MARK: - New Post // MARK: - New Post
static func newPostActivity(mentioning: Account? = nil) -> NSUserActivity { static func newPostActivity(mentioning: Account? = nil) -> NSUserActivity {
@ -41,7 +35,7 @@ class UserActivityManager {
static func handleNewPost(activity: NSUserActivity) { static func handleNewPost(activity: NSUserActivity) {
// TODO: check not currently showing compose screen // TODO: check not currently showing compose screen
let mentioning = activity.userInfo?["mentioning"] as? String let mentioning = activity.userInfo?["mentioning"] as? String
presentModally(UINavigationController(rootViewController: ComposeViewController(mentioningAcct: mentioning)), animated: true) router.present(router.compose(mentioningAcct: mentioning), animated: true)
} }
// MARK: - Check Notifications // MARK: - Check Notifications

View File

@ -11,33 +11,26 @@ import SafariServices
import Pachyderm import Pachyderm
protocol TuskerNavigationDelegate { protocol TuskerNavigationDelegate {
func viewController(forAccount accountID: String) -> UIViewController
var router: AppRouter { get }
func selected(account accountID: String) func selected(account accountID: String)
func viewController(forMention mention: Mention) -> UIViewController
func selected(mention: Mention) func selected(mention: Mention)
func viewController(forTag tag: Hashtag) -> UIViewController
func selected(tag: Hashtag) func selected(tag: Hashtag)
func viewController(forURL url: URL) -> UIViewController
func selected(url: URL) func selected(url: URL)
func viewController(forStatus statusID: String) -> UIViewController
func selected(status statusID: String) func selected(status statusID: String)
func compose() func compose()
func reply(to statusID: String) func reply(to statusID: String)
func viewController(forImage image: UIImage, description: String?, animatingFrom originView: UIView) -> UIViewController func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController
func showLargeImage(_ image: UIImage, description: String?, animatingFrom originView: UIView) func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView)
func showMoreOptions(forStatus statusID: String) func showMoreOptions(forStatus statusID: String)
@ -46,10 +39,6 @@ protocol TuskerNavigationDelegate {
extension TuskerNavigationDelegate where Self: UIViewController { extension TuskerNavigationDelegate where Self: UIViewController {
func viewController(forAccount accountID: String) -> UIViewController {
return ProfileTableViewController(accountID: accountID)
}
func selected(account accountID: String) { func selected(account accountID: String) {
// don't open if the account is the same as the current one // don't open if the account is the same as the current one
if let profileController = self as? ProfileTableViewController, if let profileController = self as? ProfileTableViewController,
@ -57,49 +46,19 @@ extension TuskerNavigationDelegate where Self: UIViewController {
return return
} }
guard let navigationController = navigationController else { router.push(router.profile(for: accountID), animated: true)
fatalError("Can't show profile VC when not in navigation controller")
}
let vc = viewController(forAccount: accountID)
navigationController.pushViewController(vc, animated: true)
}
func viewController(forMention mention: Mention) -> UIViewController {
return ProfileTableViewController(accountID: mention.id)
} }
func selected(mention: Mention) { func selected(mention: Mention) {
guard let navigationController = navigationController else { router.push(router.profile(for: mention), animated: true)
fatalError("Can't show profile VC from mention when not in navigation controller")
}
let vc = viewController(forMention: mention)
navigationController.pushViewController(vc, animated: true)
}
func viewController(forTag tag: Hashtag) -> UIViewController {
let timeline = Timeline.tag(hashtag: tag.name)
return TimelineTableViewController(for: timeline)
} }
func selected(tag: Hashtag) { func selected(tag: Hashtag) {
guard let navigationController = navigationController else { router.push(router.timeline(tag: tag), animated: true)
fatalError("Can't show hashtag timeline when not in navigation controller")
}
let vc = viewController(forTag: tag)
navigationController.pushViewController(vc, animated: true)
}
func viewController(forURL url: URL) -> UIViewController {
return SFSafariViewController(url: url)
} }
func selected(url: URL) { func selected(url: URL) {
let vc = viewController(forURL: url) router.present(router.safariViewController(for: url), animated: true)
present(vc, animated: true)
}
func viewController(forStatus statusID: String) -> UIViewController {
return ConversationTableViewController(for: statusID)
} }
func selected(status statusID: String) { func selected(status statusID: String) {
@ -109,73 +68,43 @@ extension TuskerNavigationDelegate where Self: UIViewController {
return return
} }
guard let navigationController = navigationController else { router.push(router.conversation(for: statusID), animated: true)
fatalError("Can't show conversation VC when not in navigation controller")
}
let vc = viewController(forStatus: statusID)
navigationController.pushViewController(vc, animated: true)
} }
func compose() { func compose() {
let vc = UINavigationController(rootViewController: ComposeViewController()) let vc: UINavigationController = UINavigationController(rootViewController: router.compose())
present(vc, animated: true) router.present(vc, animated: true)
} }
func reply(to statusID: String) { func reply(to statusID: String) {
let vc = UINavigationController(rootViewController: ComposeViewController(inReplyTo: statusID)) let vc = UINavigationController(rootViewController: router.compose(inReplyTo: statusID))
present(vc, animated: true) router.present(vc, animated: true)
} }
func viewController(forImage image: UIImage, description: String?, animatingFrom originView: UIView) -> UIViewController { func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController {
guard let self = self as? UIViewController & LargeImageViewControllerDelegate else { var sourceFrame = sourceView.convert(sourceView.bounds, to: view)
fatalError("Can't create large image view controller unless self is LargeImageViewControllerDelegate") if let scrollView = view as? UIScrollView {
let scale = scrollView.zoomScale
let width = sourceFrame.width * scale
let height = sourceFrame.height * scale
let x = sourceFrame.minX * scale - scrollView.contentOffset.x + scrollView.frame.minX
let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
sourceFrame = CGRect(x: x, y: y, width: width, height: height)
} }
let vc = LargeImageViewController(image: image, description: description, sourceView: originView, sourceViewController: self) let sourceCornerRadius = sourceView.layer.cornerRadius
vc.delegate = self return router.largeImage(image, description: description, sourceFrame: sourceFrame, sourceCornerRadius: sourceCornerRadius, transitioningDelegate: self)
return vc
} }
func showLargeImage(_ image: UIImage, description: String?, animatingFrom originView: UIView) { func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView) {
let vc = viewController(forImage: image, description: description, animatingFrom: originView) router.present(largeImage(image, description: description, sourceView: sourceView), animated: true)
present(vc, animated: true)
} }
func showMoreOptions(forStatus statusID: String) { func showMoreOptions(forStatus statusID: String) {
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } router.present(router.moreOptions(forStatus: statusID), animated: true)
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
if let url = status.url {
alert.addAction(UIAlertAction(title: "Open in Safari", style: .default, handler: { (_) in
let vc = SFSafariViewController(url: url)
self.present(vc, animated: true)
}))
alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { (_) in
UIPasteboard.general.url = url
}))
alert.addAction(UIAlertAction(title: "Share...", style: .default, handler: { (_) in
let vc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
self.present(vc, animated: true)
}))
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true)
} }
func showMoreOptions(forURL url: URL) { func showMoreOptions(forURL url: URL) {
let alert = UIAlertController(title: nil, message: url.absoluteString, preferredStyle: .actionSheet) router.present(router.moreOptions(forURL: url), animated: true)
alert.addAction(UIAlertAction(title: "Open in Safari", style: .default, handler: { (_) in
let vc = SFSafariViewController(url: url)
self.present(vc, animated: true)
}))
alert.addAction(UIAlertAction(title: "Copy", style: .default, handler: { (_) in
UIPasteboard.general.url = url
}))
alert.addAction(UIAlertAction(title: "Share...", style: .default, handler: { (_) in
let vc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
self.present(vc, animated: true)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true)
} }
} }

View File

@ -92,11 +92,11 @@ class ContentLabel: TTTAttributedLabel {
} }
let text = (self.text as! NSString).substring(with: link.result.range) let text = (self.text as! NSString).substring(with: link.result.range)
if let mention = getMention(for: url, text: text) { if let mention = getMention(for: url, text: text) {
return navigationDelegate.viewController(forMention: mention) return navigationDelegate.router.profile(for: mention)
} else if let tag = getHashtag(for: url, text: text) { } else if let tag = getHashtag(for: url, text: text) {
return navigationDelegate.viewController(forTag: tag) return navigationDelegate.router.timeline(tag: tag)
} else { } else {
return navigationDelegate.viewController(forURL: url) return navigationDelegate.router.safariViewController(for: url)
} }
} }

View File

@ -193,11 +193,11 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
extension ActionNotificationTableViewCell: PreviewViewControllerProvider { extension ActionNotificationTableViewCell: PreviewViewControllerProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? { func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
if avatarContainerView.frame.contains(location) { if avatarContainerView.frame.contains(location) {
return delegate?.viewController(forAccount: notification.account.id) return delegate?.router.profile(for: notification.account.id)
} else if contentLabel.frame.contains(location), } else if contentLabel.frame.contains(location),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) { let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) {
return vc return vc
} }
return delegate?.viewController(forStatus: statusID) return delegate?.router.conversation(for: statusID)
} }
} }

View File

@ -11,6 +11,8 @@ import Pachyderm
class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive { class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
var router: AppRouter!
var delegate: StatusTableViewCellDelegate? var delegate: StatusTableViewCellDelegate?
@IBOutlet weak var followLabel: UILabel! @IBOutlet weak var followLabel: UILabel!
@ -99,6 +101,6 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
extension FollowNotificationTableViewCell: PreviewViewControllerProvider { extension FollowNotificationTableViewCell: PreviewViewControllerProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? { func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
return delegate?.viewController(forAccount: accountID) return router.profile(for: accountID)
} }
} }

View File

@ -228,13 +228,13 @@ extension ConversationMainStatusTableViewCell: AttachmentViewDelegate {
extension ConversationMainStatusTableViewCell: PreviewViewControllerProvider { extension ConversationMainStatusTableViewCell: PreviewViewControllerProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? { func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
if avatarImageView.frame.contains(location) { if avatarImageView.frame.contains(location) {
return delegate?.viewController(forAccount: accountID) return delegate?.router.profile(for: accountID)
} else if attachmentsView.frame.contains(location) { } else if attachmentsView.frame.contains(location) {
let attachmentsViewLocation = attachmentsView.convert(location, from: self) let attachmentsViewLocation = attachmentsView.convert(location, from: self)
if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView { if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView {
let image = attachmentView.image! let image = attachmentView.image!
let description = attachmentView.description let description = attachmentView.description
return delegate?.viewController(forImage: image, description: description, animatingFrom: attachmentView) return delegate?.largeImage(image, description: description, sourceView: attachmentView)
} }
} else if contentLabel.frame.contains(location), } else if contentLabel.frame.contains(location),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) { let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) {

View File

@ -357,18 +357,18 @@ extension StatusTableViewCell: AttachmentViewDelegate {
extension StatusTableViewCell: PreviewViewControllerProvider { extension StatusTableViewCell: PreviewViewControllerProvider {
func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? { func getPreviewViewController(forLocation location: CGPoint, sourceViewController: UIViewController) -> UIViewController? {
if avatarImageView.frame.contains(location) { if avatarImageView.frame.contains(location) {
return delegate?.viewController(forAccount: accountID) return delegate?.router.profile(for: accountID)
} else if attachmentsView.frame.contains(location) { } else if attachmentsView.frame.contains(location) {
let attachmentsViewLocation = attachmentsView.convert(location, from: self) let attachmentsViewLocation = attachmentsView.convert(location, from: self)
if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView { if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView {
let image = attachmentView.image! let image = attachmentView.image!
let description = attachmentView.attachment.description let description = attachmentView.attachment.description
return delegate?.viewController(forImage: image, description: description, animatingFrom: attachmentView) return delegate?.largeImage(image, description: description, sourceView: attachmentView)
} }
} else if contentLabel.frame.contains(location), } else if contentLabel.frame.contains(location),
let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) { let vc = contentLabel.getViewController(forLinkAt: contentLabel.convert(location, from: self)) {
return vc return vc
} }
return delegate?.viewController(forStatus: statusID) return delegate?.router.conversation(for: statusID)
} }
} }

View File

@ -13,15 +13,9 @@ import SwiftSoup
struct XCBActions { struct XCBActions {
// MARK: - Utils // MARK: - Utils
private static func presentModally(_ vc: UIViewController, animated: Bool, completion: (() -> Void)? = nil) { private static var router: AppRouter = {
UIApplication.shared.keyWindow!.rootViewController!.present(vc, animated: animated, completion: completion) return (UIApplication.shared.delegate as! AppDelegate).router
} }()
private static func presentNav(_ vc: UIViewController, animated: Bool) {
let tabBarController = UIApplication.shared.keyWindow!.rootViewController! as! UITabBarController
let navController = tabBarController.selectedViewController as! UINavigationController
navController.pushViewController(vc, animated: animated)
}
private static func getStatus(from request: XCBRequest, session: XCBSession, completion: @escaping (Status) -> Void) { private static func getStatus(from request: XCBRequest, session: XCBSession, completion: @escaping (Status) -> Void) {
if let id = request.arguments["statusID"] { if let id = request.arguments["statusID"] {
@ -111,9 +105,9 @@ struct XCBActions {
// MARK: - Statuses // MARK: - Statuses
static func showStatus(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) { static func showStatus(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
getStatus(from: request, session: session) { (status) in getStatus(from: request, session: session) { (status) in
let vc = ConversationTableViewController(for: status.id) let vc = router.conversation(for: status.id)
DispatchQueue.main.async { DispatchQueue.main.async {
presentNav(vc, animated: true) router.push(vc, animated: true)
} }
} }
} }
@ -146,10 +140,10 @@ struct XCBActions {
} }
} }
} else { } else {
let compose = ComposeViewController(mentioningAcct: mentioning, text: text) let compose = router.compose(mentioningAcct: mentioning, text: text)
compose.xcbSession = session compose.xcbSession = session
let vc = UINavigationController(rootViewController: compose) let vc = UINavigationController(rootViewController: compose)
presentModally(vc, animated: true) router.present(vc, animated: true)
} }
} }
@ -213,9 +207,9 @@ struct XCBActions {
if silent ?? false { if silent ?? false {
performAction(status: status, completion: nil) performAction(status: status, completion: nil)
} else { } else {
let vc = ConversationTableViewController(for: status.id) let vc = router.conversation(for: status.id)
DispatchQueue.main.async { DispatchQueue.main.async {
presentNav(vc, animated: true) router.push(vc, animated: true)
} }
let alertController = UIAlertController(title: alertTitle, message: nil, preferredStyle: .alert) let alertController = UIAlertController(title: alertTitle, message: nil, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in
@ -229,7 +223,7 @@ struct XCBActions {
session.complete(with: .cancel) session.complete(with: .cancel)
})) }))
DispatchQueue.main.async { DispatchQueue.main.async {
presentModally(alertController, animated: true) router.present(alertController, animated: true)
} }
} }
} }
@ -240,9 +234,9 @@ struct XCBActions {
// MARK: - Accounts // MARK: - Accounts
static func showAccount(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) { static func showAccount(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
getAccount(from: request, session: session) { (account) in getAccount(from: request, session: session) { (account) in
let vc = ProfileTableViewController(accountID: account.id) let vc = router.profile(for: account.id)
DispatchQueue.main.async { DispatchQueue.main.async {
presentNav(vc, animated: true) router.push(vc, animated: true)
} }
} }
} }
@ -297,9 +291,9 @@ struct XCBActions {
if silent ?? false { if silent ?? false {
performAction(account) performAction(account)
} else { } else {
let vc = ProfileTableViewController(accountID: account.id) let vc = router.profile(for: account.id)
DispatchQueue.main.async { DispatchQueue.main.async {
presentNav(vc, animated: true) router.push(vc, animated: true)
} }
let alertController = UIAlertController(title: "Follow \(account.realDisplayName)?", message: nil, preferredStyle: .alert) let alertController = UIAlertController(title: "Follow \(account.realDisplayName)?", message: nil, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in
@ -309,7 +303,7 @@ struct XCBActions {
session.complete(with: .cancel) session.complete(with: .cancel)
})) }))
DispatchQueue.main.async { DispatchQueue.main.async {
presentModally(alertController, animated: true) router.present(alertController, animated: true)
} }
} }
} }