Files
GitHawk/Classes/Repository/RepositoryViewController.swift
2017-09-18 22:50:26 -04:00

273 lines
9.5 KiB
Swift

//
// RepositoryViewController.swift
// Freetime
//
// Created by Sherlock, James on 29/07/2017.
// Copyright © 2017 Ryan Nystrom. All rights reserved.
//
import UIKit
import IGListKit
import TUSafariActivity
import SafariServices
struct RepositoryDetails {
let owner: String
let name: String
let hasIssuesEnabled: Bool
}
class RepositoryViewController: UIViewController,
FeedDelegate,
ListAdapterDataSource,
SegmentedControlSectionControllerDelegate,
LoadMoreSectionControllerDelegate,
PrimaryViewController {
private let repo: RepositoryDetails
private let client: RepositoryClient
private lazy var feed: Feed = { Feed(viewController: self, delegate: self) }()
private let noReadmeResultsKey = "noReadmeResultsKey" as ListDiffable
private let noIssuesResultsKey = "noIssuesResultsKey" as ListDiffable
private let noPullRequestsResultsKey = "noPullRequestsResultsKey" as ListDiffable
private let dataSource: RepositoryDataSource
init(client: GithubClient, repo: RepositoryDetails) {
self.repo = repo
self.client = RepositoryClient(githubClient: client, owner: repo.owner, name: repo.name)
self.dataSource = RepositoryDataSource(hasIssuesEnabled: repo.hasIssuesEnabled)
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
feed.viewDidLoad()
feed.adapter.dataSource = self
title = "\(repo.owner)/\(repo.name)"
let rightItem = UIBarButtonItem(
image: UIImage(named: "bullets-hollow"),
style: .plain,
target: self,
action: #selector(IssuesViewController.onMore(sender:))
)
rightItem.accessibilityLabel = NSLocalizedString("More options", comment: "")
navigationItem.rightBarButtonItem = rightItem
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
rz_smoothlyDeselectRows(collectionView: feed.collectionView)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
feed.viewWillLayoutSubviews(view: view)
}
// MARK: Private API
var repoUrl: URL {
return URL(string: "https://github.com/\(repo.owner)/\(repo.name)")!
}
var repoOwnerUrl: URL {
return URL(string: "https://github.com/\(repo.owner)")!
}
func shareAction(sender: UIBarButtonItem) -> UIAlertAction {
return UIAlertAction(title: NSLocalizedString("Send To", comment: ""), style: .default) { [weak self] _ in
guard let strongSelf = self else { return }
let safariActivity = TUSafariActivity()
let controller = UIActivityViewController(activityItems: [strongSelf.repoUrl], applicationActivities: [safariActivity])
controller.popoverPresentationController?.barButtonItem = sender
strongSelf.present(controller, animated: true)
}
}
func safariAction() -> UIAlertAction {
return UIAlertAction(title: NSLocalizedString("Open in Safari", comment: ""), style: .default) { [weak self] _ in
guard let strongSelf = self else { return }
let controller = SFSafariViewController(url: strongSelf.repoUrl)
strongSelf.present(controller, animated: true)
}
}
func viewOwnerAction() -> UIAlertAction {
return UIAlertAction(title: NSLocalizedString("View Owner's Profile", comment: ""), style: .default) { [weak self] _ in
guard let strongSelf = self else { return }
let controller = SFSafariViewController(url: strongSelf.repoOwnerUrl)
strongSelf.present(controller, animated: true)
}
}
func onMore(sender: UIBarButtonItem) {
let alert = UIAlertController()
alert.addAction(shareAction(sender: sender))
alert.addAction(safariAction())
alert.addAction(viewOwnerAction())
alert.popoverPresentationController?.barButtonItem = sender
present(alert, animated: true)
}
// MARK: Data Loading/Paging
private func update(dismissRefresh: Bool, animated: Bool) {
feed.finishLoading(dismissRefresh: dismissRefresh, animated: animated)
}
func reload() {
client.fetchReadme { result in
switch result {
case .error: break
case .success(let readme):
self.dataSource.setReadme(
readme,
width: self.view.bounds.width,
owner: self.repo.owner,
repo: self.repo.name,
completion: {
self.update(dismissRefresh: true, animated: true)
})
}
}
client.load(containerWidth: view.bounds.width) { result in
switch result {
case .error: break
case .success(let payload):
self.dataSource.reset(
issues: payload.issues.models,
issuesNextPage: payload.issues.nextPage,
pullRequests: payload.pullRequests.models,
pullRequestsNextPage: payload.pullRequests.nextPage
)
self.update(dismissRefresh: true, animated: true)
}
}
}
func loadNextPage() {
let width = view.bounds.width
switch dataSource.state {
case .readme: return
case .issues:
client.loadMoreIssues(nextPage: dataSource.issuesNextPage, containerWidth: width, completion: { result in
switch result {
case .error: break
case .success(let payload):
self.dataSource.appendIssues(issues: payload.models, page: payload.nextPage)
self.update(dismissRefresh: true, animated: false)
}
})
case .pullRequests:
client.loadMorePullRequests(nextPage: dataSource.pullRequestsNextPage, containerWidth: width, completion: { result in
switch result {
case .error: break
case .success(let payload):
self.dataSource.appendPullRequests(pullRequests: payload.models, page: payload.nextPage)
self.update(dismissRefresh: true, animated: false)
}
})
}
}
// MARK: FeedDelegate
func loadFromNetwork(feed: Feed) {
reload()
}
func loadNextPage(feed: Feed) -> Bool {
return false
}
// MARK: ListAdapterDataSource
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var builder = [ListDiffable]()
builder.append(dataSource.selection)
let models = dataSource.selectionModels
builder += models
if models.count == 0, feed.status == .idle {
switch dataSource.state {
case .readme: builder.append(noReadmeResultsKey)
case .issues: builder.append(noIssuesResultsKey)
case .pullRequests: builder.append(noPullRequestsResultsKey)
}
}
return builder
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
guard let object = object as? ListDiffable else { fatalError("Object does not conform to ListDiffable") }
// 28 is the default height of UISegmentedControl
let controlHeight = 28 + 2*Styles.Sizes.rowSpacing
if object === noReadmeResultsKey {
return RepositoryEmptyResultsSectionController(
topInset: controlHeight,
topLayoutGuide: topLayoutGuide,
bottomLayoutGuide: bottomLayoutGuide,
type: .readme
)
} else if object === noIssuesResultsKey {
return RepositoryEmptyResultsSectionController(
topInset: controlHeight,
topLayoutGuide: topLayoutGuide,
bottomLayoutGuide: bottomLayoutGuide,
type: .issues
)
} else if object === noPullRequestsResultsKey {
return RepositoryEmptyResultsSectionController(
topInset: controlHeight,
topLayoutGuide: topLayoutGuide,
bottomLayoutGuide: bottomLayoutGuide,
type: .pullRequests
)
} else if object === dataSource.selection {
return SegmentedControlSectionController(delegate: self, height: controlHeight)
} else if object === dataSource.loadMore {
return LoadMoreSectionController(delegate: self)
} else if object is RepositoryIssueSummaryModel {
return RepositorySummarySectionController(client: client.githubClient, repo: repo)
} else if object is RepositoryReadmeModel {
return RepositoryReadmeSectionController()
}
fatalError("Could not find section controller for object")
}
func emptyView(for listAdapter: ListAdapter) -> UIView? {
return nil
}
// MARK: SegmentedControlSectionControllerDelegate
func didChangeSelection(sectionController: SegmentedControlSectionController, model: SegmentedControlModel) {
update(dismissRefresh: false, animated: false)
}
// MARK: LoadMoreSectionControllerDelegate
func didSelect(sectionController: LoadMoreSectionController) {
loadNextPage()
}
}