diff --git a/Classes/Repository/RepositoryBranches/GitHubClient+RepositoryBranches.swift b/Classes/Repository/RepositoryBranches/GitHubClient+RepositoryBranches.swift new file mode 100644 index 000000000..38d1d6679 --- /dev/null +++ b/Classes/Repository/RepositoryBranches/GitHubClient+RepositoryBranches.swift @@ -0,0 +1,38 @@ +// +// GitHubClient+RepoBranches.swift +// Freetime +// +// Created by B_Litwin on 9/25/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import GitHubAPI + +extension GithubClient { + + func fetchRepositoryBranches(owner: String, + repo: String, + completion: @escaping (Result<([String])>)->Void + ) { + let query = FetchRepositoryBranchesQuery(owner: owner, name: repo) + client.query(query, result: { $0.repository }) { result in + + switch result { + case .failure(let error): + completion(.error(error)) + + case .success(let repository): + var branches: [String] = [] + repository.refs.map { edges in + edges.edges.map { edge in + branches += edge.compactMap { + $0?.node?.name + } + } + } + + completion(.success(branches)) + } + } + } +} diff --git a/Classes/Repository/RepositoryBranches/RepositoryBranchUpdatable.swift b/Classes/Repository/RepositoryBranches/RepositoryBranchUpdatable.swift new file mode 100644 index 000000000..f0924fdf8 --- /dev/null +++ b/Classes/Repository/RepositoryBranches/RepositoryBranchUpdatable.swift @@ -0,0 +1,13 @@ +// +// RepositoryBranchUpdatable.swift +// Freetime +// +// Created by B_Litwin on 9/28/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import UIKit + +protocol RepositoryBranchUpdatable: class { + func updateBranch(to newBranch: String) +} diff --git a/Classes/Repository/RepositoryBranches/RepositoryBranchesCell.swift b/Classes/Repository/RepositoryBranches/RepositoryBranchesCell.swift new file mode 100644 index 000000000..beff55500 --- /dev/null +++ b/Classes/Repository/RepositoryBranches/RepositoryBranchesCell.swift @@ -0,0 +1,58 @@ +// +// RepoBranchesCell.swift +// Freetime +// +// Created by B_Litwin on 9/25/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import UIKit + +final class RepositoryBranchCell: SelectableCell { + public let label = UILabel() + private let checkedImageView = UIImageView(image: UIImage(named: "check-small")?.withRenderingMode(.alwaysTemplate)) + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = nil + contentView.backgroundColor = nil + + contentView.addSubview(checkedImageView) + checkedImageView.tintColor = Styles.Colors.Blue.medium.color + checkedImageView.snp.makeConstraints { make in + make.right.equalTo(-Styles.Sizes.gutter) + make.centerY.equalTo(contentView.snp.centerY) + } + + contentView.addSubview(label) + label.font = Styles.Text.bodyBold.preferredFont + label.textColor = .white + label.snp.makeConstraints { make in + make.left.equalTo(Styles.Sizes.gutter) + make.right.lessThanOrEqualTo(checkedImageView.snp.left) + make.centerY.equalTo(contentView.snp.centerY) + } + + let border = contentView.addBorder(.bottom, + left: Styles.Sizes.gutter, + right: -Styles.Sizes.gutter + ) + border.backgroundColor = Styles.Colors.Gray.medium.color + } + + func setSelected(_ selected: Bool) { + checkedImageView.isHidden = !selected + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Accessibility + + override var accessibilityLabel: String? { + get { return AccessibilityHelper.generatedLabel(forCell: self) } + set { } + } +} diff --git a/Classes/Repository/RepositoryBranches/RepositoryBranchesSectionController.swift b/Classes/Repository/RepositoryBranches/RepositoryBranchesSectionController.swift new file mode 100644 index 000000000..858669ca6 --- /dev/null +++ b/Classes/Repository/RepositoryBranches/RepositoryBranchesSectionController.swift @@ -0,0 +1,43 @@ +// +// RepoBranchesSectionController.swift +// Freetime +// +// Created by B_Litwin on 9/25/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import UIKit +import IGListKit + +protocol RepositoryBranchSectionControllerDelegate: class { + func didSelect(value: RepositoryBranchViewModel) +} + +final class RepositoryBranchSectionController: ListSwiftSectionController { + + public weak var delegate: RepositoryBranchSectionControllerDelegate? + + override func createBinders(from value: RepositoryBranchViewModel) -> [ListBinder] { + return [ + binder( + value, + cellType: ListCellType.class(RepositoryBranchCell.self), + size: { + return CGSize( + width: $0.collection.containerSize.width, + height: Styles.Sizes.tableCellHeightLarge + ) + }, + configure: { + $0.label.text = $1.value.branch + $0.setSelected($1.value.selected) + }, + didSelect: { [weak self] context in + guard let strongSelf = self else { return } + context.deselect(animated: true) + strongSelf.delegate?.didSelect(value: context.value) + }) + ] + } +} + diff --git a/Classes/Repository/RepositoryBranches/RepositoryBranchesViewController.swift b/Classes/Repository/RepositoryBranches/RepositoryBranchesViewController.swift new file mode 100644 index 000000000..63d82e8e1 --- /dev/null +++ b/Classes/Repository/RepositoryBranches/RepositoryBranchesViewController.swift @@ -0,0 +1,85 @@ +// +// RepoBranchesViewController.swift +// Freetime +// +// Created by B_Litwin on 9/25/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import UIKit +import Squawk +import IGListKit + +final class RepositoryBranchesViewController: BaseListViewController2, +BaseListViewController2DataSource, +RepositoryBranchSectionControllerDelegate +{ + + private let owner: String + private let repo: String + private let client: GithubClient + private var branches: [String] = [] + public var branch: String + + init(branch: String, + owner: String, + repo: String, + client: GithubClient + ) + { + self.branch = branch + self.owner = owner + self.repo = repo + self.client = client + super.init(emptyErrorMessage: "Couldn't load repository branches") + + title = NSLocalizedString("Branches", comment: "") + preferredContentSize = Styles.Sizes.contextMenuSize + feed.collectionView.backgroundColor = Styles.Colors.menuBackgroundColor.color + dataSource = self + } + + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white] + addMenuDoneButton() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func fetch(page: String?) { + client.fetchRepositoryBranches(owner: owner, + repo: repo + ) + { [weak self] result in + switch result { + case .success(let branches): + self?.branches = branches + case .error: + Squawk.showError(message: "Couldn't fetch repository branches") + } + self?.update(animated: true) + } + } + + func models(adapter: ListSwiftAdapter) -> [ListSwiftPair] { + guard feed.status == .idle else { return [] } + return branches.map { + let value = RepositoryBranchViewModel(branch: $0, + selected: $0 == self.branch) + + return ListSwiftPair(value) { [weak self] in + let controller = RepositoryBranchSectionController() + controller.delegate = self + return controller + } + } + } + + func didSelect(value: RepositoryBranchViewModel) { + self.branch = value.branch + fetch(page: nil) + } +} diff --git a/Classes/Repository/RepositoryBranches/RepositoryBranchesViewModel.swift b/Classes/Repository/RepositoryBranches/RepositoryBranchesViewModel.swift new file mode 100644 index 000000000..be4f07edd --- /dev/null +++ b/Classes/Repository/RepositoryBranches/RepositoryBranchesViewModel.swift @@ -0,0 +1,25 @@ +// +// RepoBranchesViewModel.swift +// Freetime +// +// Created by B_Litwin on 9/25/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import IGListKit + +struct RepositoryBranchViewModel: ListSwiftDiffable { + let branch: String + let selected: Bool + + var identifier: String { + return branch + } + + func isEqual(to value: ListSwiftDiffable) -> Bool { + guard let value = value as? RepositoryBranchViewModel else { return false } + return value.branch == branch + && value.selected == selected + } +} + diff --git a/Classes/Repository/RepositoryCodeDirectoryViewController.swift b/Classes/Repository/RepositoryCodeDirectoryViewController.swift index 7996ddc34..60eb8adad 100644 --- a/Classes/Repository/RepositoryCodeDirectoryViewController.swift +++ b/Classes/Repository/RepositoryCodeDirectoryViewController.swift @@ -11,10 +11,12 @@ import IGListKit final class RepositoryCodeDirectoryViewController: BaseListViewController, BaseListViewControllerDataSource, -ListSingleSectionControllerDelegate { +ListSingleSectionControllerDelegate, +RepositoryBranchUpdatable +{ private let client: GithubClient - private let branch: String + private var branch: String private let path: FilePath private let repo: RepositoryDetails private var files = [RepositoryFile]() @@ -177,5 +179,13 @@ extension RepositoryCodeDirectoryViewController { navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled) } + + //Mark: RepositoryBranchUpdatable + + func updateBranch(to newBranch: String) { + guard self.branch != newBranch else { return } + self.branch = newBranch + fetch(page: nil) + } } diff --git a/Classes/Repository/RepositoryOverviewViewController.swift b/Classes/Repository/RepositoryOverviewViewController.swift index ddccc9052..a8bc7bfac 100644 --- a/Classes/Repository/RepositoryOverviewViewController.swift +++ b/Classes/Repository/RepositoryOverviewViewController.swift @@ -20,11 +20,14 @@ class HackScrollIndicatorInsetsCollectionView: UICollectionView { } class RepositoryOverviewViewController: BaseListViewController, -BaseListViewControllerDataSource { +BaseListViewControllerDataSource, +RepositoryBranchUpdatable +{ private let repo: RepositoryDetails private let client: RepositoryClient private var readme: RepositoryReadmeModel? + private var branch: String // lazy var _feed: Feed = { Feed( // viewController: self, @@ -41,6 +44,7 @@ BaseListViewControllerDataSource { init(client: GithubClient, repo: RepositoryDetails) { self.repo = repo self.client = RepositoryClient(githubClient: client, owner: repo.owner, name: repo.name) + self.branch = repo.defaultBranch super.init( emptyErrorMessage: NSLocalizedString("Cannot load README.", comment: "") ) @@ -71,20 +75,13 @@ BaseListViewControllerDataSource { // let contentInset = feed.collectionView.contentInset let width = view.bounds.width - Styles.Sizes.gutter * 2 let contentSizeCategory = UIContentSizeCategory.preferred + let branch = self.branch client.githubClient.client - .send(V3RepositoryReadmeRequest(owner: repo.owner, repo: repo.name)) { [weak self] result in + .send(V3RepositoryReadmeRequest(owner: repo.owner, repo: repo.name, branch: branch)) { [weak self] result in switch result { case .success(let response): DispatchQueue.global().async { - let branch: String - if let items = URLComponents(url: response.data.url, resolvingAgainstBaseURL: false)?.queryItems, - let index = items.index(where: { $0.name == "ref" }), - let value = items[index].value { - branch = value - } else { - branch = "master" - } let models = MarkdownModels( response.data.content, @@ -130,5 +127,13 @@ BaseListViewControllerDataSource { type: .readme ) } + + //Mark: RepositoryBranchUpdatable + + func updateBranch(to newBranch: String) { + guard self.branch != newBranch else { return } + self.branch = newBranch + fetch(page: nil) + } } diff --git a/Classes/Repository/RepositoryViewController.swift b/Classes/Repository/RepositoryViewController.swift index a3f152398..a2c7bbb7d 100644 --- a/Classes/Repository/RepositoryViewController.swift +++ b/Classes/Repository/RepositoryViewController.swift @@ -12,15 +12,18 @@ import Pageboy import TUSafariActivity import SafariServices import Squawk +import ContextMenu class RepositoryViewController: TabmanViewController, PageboyViewControllerDataSource, -NewIssueTableViewControllerDelegate { +NewIssueTableViewControllerDelegate, +ContextMenuDelegate { private let repo: RepositoryDetails private let client: GithubClient private let controllers: [UIViewController] private var bookmarkNavController: BookmarkNavigationController? = nil + public private(set) var branch: String var moreOptionsItem: UIBarButtonItem { let rightItem = UIBarButtonItem(image: UIImage(named: "bullets-hollow"), target: self, action: #selector(RepositoryViewController.onMore(sender:))) @@ -31,6 +34,7 @@ NewIssueTableViewControllerDelegate { init(client: GithubClient, repo: RepositoryDetails) { self.repo = repo self.client = client + self.branch = repo.defaultBranch let bookmark = Bookmark( type: .repo, @@ -109,6 +113,33 @@ NewIssueTableViewControllerDelegate { } navigationItem.rightBarButtonItems = items } + + func switchBranchAction() -> UIAlertAction { + return UIAlertAction( + title: NSLocalizedString("Switch Branch", comment: ""), + style: .default + ) { + [weak self] action in + guard let strongSelf = self else { return } + let viewController = + RepositoryBranchesViewController( + branch: strongSelf.branch, + owner: strongSelf.repo.owner, + repo: strongSelf.repo.name, + client: strongSelf.client + ) + + strongSelf.showContextualMenu( + viewController, + options: ContextMenu.Options( + containerStyle: ContextMenu.ContainerStyle( + backgroundColor: Styles.Colors.menuBackgroundColor.color + ) + ), + delegate: self + ) + } + } func newIssueAction() -> UIAlertAction? { guard let newIssueViewController = NewIssueTableViewController.create( @@ -129,7 +160,7 @@ NewIssueTableViewControllerDelegate { } @objc func onMore(sender: UIButton) { - let alertTitle = "\(repo.owner)/\(repo.name)" + let alertTitle = "\(repo.owner)/\(repo.name):\(branch)" let alert = UIAlertController.configured(title: alertTitle, preferredStyle: .actionSheet) weak var weakSelf = self @@ -140,6 +171,7 @@ NewIssueTableViewControllerDelegate { AlertAction(alertBuilder).share([repoUrl], activities: [TUSafariActivity()]) { $0.popoverPresentationController?.setSourceView(sender) }, + switchBranchAction(), AlertAction.cancel() ]) alert.popoverPresentationController?.setSourceView(sender) @@ -167,5 +199,19 @@ NewIssueTableViewControllerDelegate { let issuesViewController = IssuesViewController(client: client, model: model) show(issuesViewController, sender: self) } + + //MARK: ContextMenuDelegate + + func contextMenuWillDismiss(viewController: UIViewController, animated: Bool) { + guard let viewController = viewController as? RepositoryBranchesViewController else { return } + self.branch = viewController.branch + controllers.forEach { + if let branchUpdatable = $0 as? RepositoryBranchUpdatable { + branchUpdatable.updateBranch(to: viewController.branch) + } + } + } + + func contextMenuDidDismiss(viewController: UIViewController, animated: Bool) {} } diff --git a/Freetime.xcodeproj/project.pbxproj b/Freetime.xcodeproj/project.pbxproj index 5e348165f..e66fde29b 100644 --- a/Freetime.xcodeproj/project.pbxproj +++ b/Freetime.xcodeproj/project.pbxproj @@ -439,6 +439,12 @@ 98F9F4031F9CD006005A0266 /* Image+Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F9F4021F9CD006005A0266 /* Image+Base64.swift */; }; BD3761B0209E032500401DFB /* BookmarkNavigationItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3761AF209E032500401DFB /* BookmarkNavigationItemTests.swift */; }; BD89007E20B8844B0026013F /* NetworkingURLPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD89007D20B8844B0026013F /* NetworkingURLPathTests.swift */; }; + BDB6AA66215FBC35009BB73C /* RepositoryBranchesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA5F215FBC35009BB73C /* RepositoryBranchesViewModel.swift */; }; + BDB6AA67215FBC35009BB73C /* RepositoryBranchUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA60215FBC35009BB73C /* RepositoryBranchUpdatable.swift */; }; + BDB6AA68215FBC35009BB73C /* RepositoryBranchesSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA61215FBC35009BB73C /* RepositoryBranchesSectionController.swift */; }; + BDB6AA69215FBC35009BB73C /* RepositoryBranchesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA62215FBC35009BB73C /* RepositoryBranchesViewController.swift */; }; + BDB6AA6A215FBC35009BB73C /* RepositoryBranchesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA63215FBC35009BB73C /* RepositoryBranchesCell.swift */; }; + BDB6AA6B215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA64215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift */; }; D8BAD0601FDA0A1A00C41071 /* LabelListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD05F1FDA0A1A00C41071 /* LabelListCell.swift */; }; D8BAD0641FDF221900C41071 /* LabelListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD0631FDF221900C41071 /* LabelListView.swift */; }; D8BAD0661FDF224600C41071 /* WrappingStaticSpacingFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD0651FDF224600C41071 /* WrappingStaticSpacingFlowLayout.swift */; }; @@ -971,6 +977,12 @@ B3C439BE890EECD7C0C692C5 /* Pods-Freetime.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Freetime.testflight.xcconfig"; path = "Pods/Target Support Files/Pods-Freetime/Pods-Freetime.testflight.xcconfig"; sourceTree = ""; }; BD3761AF209E032500401DFB /* BookmarkNavigationItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkNavigationItemTests.swift; sourceTree = ""; }; BD89007D20B8844B0026013F /* NetworkingURLPathTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingURLPathTests.swift; sourceTree = ""; }; + BDB6AA5F215FBC35009BB73C /* RepositoryBranchesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryBranchesViewModel.swift; sourceTree = ""; }; + BDB6AA60215FBC35009BB73C /* RepositoryBranchUpdatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryBranchUpdatable.swift; sourceTree = ""; }; + BDB6AA61215FBC35009BB73C /* RepositoryBranchesSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryBranchesSectionController.swift; sourceTree = ""; }; + BDB6AA62215FBC35009BB73C /* RepositoryBranchesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryBranchesViewController.swift; sourceTree = ""; }; + BDB6AA63215FBC35009BB73C /* RepositoryBranchesCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryBranchesCell.swift; sourceTree = ""; }; + BDB6AA64215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GitHubClient+RepositoryBranches.swift"; sourceTree = ""; }; D396E0DA66FED629384A84BC /* Pods_FreetimeWatch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FreetimeWatch.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D8BAD05F1FDA0A1A00C41071 /* LabelListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelListCell.swift; sourceTree = ""; }; D8BAD0631FDF221900C41071 /* LabelListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelListView.swift; sourceTree = ""; }; @@ -2032,6 +2044,7 @@ 986B87371F2CB29700AAB55C /* RepositorySummaryCell.swift */, 986B87351F2CB28C00AAB55C /* RepositorySummarySectionController.swift */, 2905AFAE1F7357FA0015AE32 /* RepositoryViewController.swift */, + BDB6AA5D215FBC35009BB73C /* RepositoryBranches */, ); path = Repository; sourceTree = ""; @@ -2083,6 +2096,19 @@ path = "Image Upload"; sourceTree = ""; }; + BDB6AA5D215FBC35009BB73C /* RepositoryBranches */ = { + isa = PBXGroup; + children = ( + BDB6AA5F215FBC35009BB73C /* RepositoryBranchesViewModel.swift */, + BDB6AA60215FBC35009BB73C /* RepositoryBranchUpdatable.swift */, + BDB6AA61215FBC35009BB73C /* RepositoryBranchesSectionController.swift */, + BDB6AA62215FBC35009BB73C /* RepositoryBranchesViewController.swift */, + BDB6AA63215FBC35009BB73C /* RepositoryBranchesCell.swift */, + BDB6AA64215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift */, + ); + path = RepositoryBranches; + sourceTree = ""; + }; CF4CC0BFE456879DD6DBC714 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -2720,6 +2746,7 @@ 294434E11FB1F2DA00050C06 /* BookmarkNavigationController.swift in Sources */, 29BCA7FE212137E100753A3C /* IssueTargetBranchModel.swift in Sources */, 29EE44461F19D5C100B05ED3 /* GithubClient+Issues.swift in Sources */, + BDB6AA66215FBC35009BB73C /* RepositoryBranchesViewModel.swift in Sources */, 299F63E4205E1CAB0015D901 /* UIViewController+StyledTextViewCellDelegate.swift in Sources */, 29B94E6D1FCB472400715D7E /* IssueFileChangesModel.swift in Sources */, 29242810210A50BE001F5980 /* SpacerModel.swift in Sources */, @@ -2889,6 +2916,7 @@ 292FF8B01F2FDC33009E63F7 /* IssueTextActionsView.swift in Sources */, 292FCB181EDFCC510026635E /* IssueTitleCell.swift in Sources */, 292FCB191EDFCC510026635E /* IssueTitleSectionController.swift in Sources */, + BDB6AA67215FBC35009BB73C /* RepositoryBranchUpdatable.swift in Sources */, 294563F01EE5036A00DBCD35 /* IssueType.swift in Sources */, 29136BDD200A6C40007317BE /* IssueTitleModel.swift in Sources */, 2919294B1F3EAD890012067B /* IssueViewFilesCell.swift in Sources */, @@ -2916,6 +2944,7 @@ 2999972C20311DD700995FFD /* IssueMergeSummaryCell.swift in Sources */, 290EF5791F06BAF4006A2160 /* NoNewNotificationsSectionController.swift in Sources */, 290EF56A1F06A821006A2160 /* Notification+NotificationViewModel.swift in Sources */, + BDB6AA6A215FBC35009BB73C /* RepositoryBranchesCell.swift in Sources */, 29792B1B1FFB21AD007A0C57 /* AutocompleteController.swift in Sources */, 29D548CB1FA27FE900F8E46F /* UINavigationItem+TitleSubtitle.swift in Sources */, 299A04A11FAE86B0003C2450 /* IssueReviewViewCommentsCell.swift in Sources */, @@ -2925,6 +2954,8 @@ 29A1950C1EC7901400C3E289 /* NotificationType.swift in Sources */, 294AF7F11FC674A400854790 /* CommitDetails.swift in Sources */, 29D8DF031FBBD72A00C486C2 /* IssueManagingActionModel.swift in Sources */, + BDB6AA68215FBC35009BB73C /* RepositoryBranchesSectionController.swift in Sources */, + BDB6AA6B215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift in Sources */, 292CD3D41F0DC12100D3D57B /* PhotoViewHandler.swift in Sources */, 294563EE1EE5012900DBCD35 /* PullRequest+IssueType.swift in Sources */, 298003401F51E93B00BE90F4 /* RatingCell.swift in Sources */, @@ -2967,6 +2998,7 @@ 986B87361F2CB28C00AAB55C /* RepositorySummarySectionController.swift in Sources */, 2905AFAF1F7357FA0015AE32 /* RepositoryViewController.swift in Sources */, 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 */, diff --git a/Local Pods/GitHubAPI/GitHubAPI/V3RepositoryReadmeRequest.swift b/Local Pods/GitHubAPI/GitHubAPI/V3RepositoryReadmeRequest.swift index f738c8ceb..25454462a 100644 --- a/Local Pods/GitHubAPI/GitHubAPI/V3RepositoryReadmeRequest.swift +++ b/Local Pods/GitHubAPI/GitHubAPI/V3RepositoryReadmeRequest.swift @@ -13,12 +13,18 @@ public struct V3RepositoryReadmeRequest: V3Request { public var pathComponents: [String] { return ["repos", owner, repo, "readme"] } + + public var parameters: [String : Any]? { + return ["ref" : branch] + } public let owner: String public let repo: String + public let branch: String - public init(owner: String, repo: String) { + public init(owner: String, repo: String, branch: String) { self.owner = owner self.repo = repo + self.branch = branch } } diff --git a/gql/API.swift b/gql/API.swift index 4b5033d16..55adaf6f3 100644 --- a/gql/API.swift +++ b/gql/API.swift @@ -14043,6 +14043,203 @@ public final class RemoveReactionMutation: GraphQLMutation { } } +public final class FetchRepositoryBranchesQuery: GraphQLQuery { + public static let operationString = + "query fetchRepositoryBranches($owner: String!, $name: String!) {\n repository(owner: $owner, name: $name) {\n __typename\n refs(first: 50, refPrefix: \"refs/heads/\") {\n __typename\n edges {\n __typename\n node {\n __typename\n name\n }\n }\n }\n }\n}" + + public var owner: String + public var name: String + + public init(owner: String, name: String) { + self.owner = owner + self.name = name + } + + public var variables: GraphQLMap? { + return ["owner": owner, "name": name] + } + + public struct Data: GraphQLSelectionSet { + public static let possibleTypes = ["Query"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField("repository", arguments: ["owner": GraphQLVariable("owner"), "name": GraphQLVariable("name")], type: .object(Repository.selections)), + ] + + public var snapshot: Snapshot + + public init(snapshot: Snapshot) { + self.snapshot = snapshot + } + + public init(repository: Repository? = nil) { + self.init(snapshot: ["__typename": "Query", "repository": repository.flatMap { (value: Repository) -> Snapshot in value.snapshot }]) + } + + /// Lookup a given repository by the owner and repository name. + public var repository: Repository? { + get { + return (snapshot["repository"] as? Snapshot).flatMap { Repository(snapshot: $0) } + } + set { + snapshot.updateValue(newValue?.snapshot, forKey: "repository") + } + } + + public struct Repository: GraphQLSelectionSet { + public static let possibleTypes = ["Repository"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField("__typename", type: .nonNull(.scalar(String.self))), + GraphQLField("refs", arguments: ["first": 50, "refPrefix": "refs/heads/"], type: .object(Ref.selections)), + ] + + public var snapshot: Snapshot + + public init(snapshot: Snapshot) { + self.snapshot = snapshot + } + + public init(refs: Ref? = nil) { + self.init(snapshot: ["__typename": "Repository", "refs": refs.flatMap { (value: Ref) -> Snapshot in value.snapshot }]) + } + + public var __typename: String { + get { + return snapshot["__typename"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: "__typename") + } + } + + /// Fetch a list of refs from the repository + public var refs: Ref? { + get { + return (snapshot["refs"] as? Snapshot).flatMap { Ref(snapshot: $0) } + } + set { + snapshot.updateValue(newValue?.snapshot, forKey: "refs") + } + } + + public struct Ref: GraphQLSelectionSet { + public static let possibleTypes = ["RefConnection"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField("__typename", type: .nonNull(.scalar(String.self))), + GraphQLField("edges", type: .list(.object(Edge.selections))), + ] + + public var snapshot: Snapshot + + public init(snapshot: Snapshot) { + self.snapshot = snapshot + } + + public init(edges: [Edge?]? = nil) { + self.init(snapshot: ["__typename": "RefConnection", "edges": edges.flatMap { (value: [Edge?]) -> [Snapshot?] in value.map { (value: Edge?) -> Snapshot? in value.flatMap { (value: Edge) -> Snapshot in value.snapshot } } }]) + } + + public var __typename: String { + get { + return snapshot["__typename"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: "__typename") + } + } + + /// A list of edges. + public var edges: [Edge?]? { + get { + return (snapshot["edges"] as? [Snapshot?]).flatMap { (value: [Snapshot?]) -> [Edge?] in value.map { (value: Snapshot?) -> Edge? in value.flatMap { (value: Snapshot) -> Edge in Edge(snapshot: value) } } } + } + set { + snapshot.updateValue(newValue.flatMap { (value: [Edge?]) -> [Snapshot?] in value.map { (value: Edge?) -> Snapshot? in value.flatMap { (value: Edge) -> Snapshot in value.snapshot } } }, forKey: "edges") + } + } + + public struct Edge: GraphQLSelectionSet { + public static let possibleTypes = ["RefEdge"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField("__typename", type: .nonNull(.scalar(String.self))), + GraphQLField("node", type: .object(Node.selections)), + ] + + public var snapshot: Snapshot + + public init(snapshot: Snapshot) { + self.snapshot = snapshot + } + + public init(node: Node? = nil) { + self.init(snapshot: ["__typename": "RefEdge", "node": node.flatMap { (value: Node) -> Snapshot in value.snapshot }]) + } + + public var __typename: String { + get { + return snapshot["__typename"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: "__typename") + } + } + + /// The item at the end of the edge. + public var node: Node? { + get { + return (snapshot["node"] as? Snapshot).flatMap { Node(snapshot: $0) } + } + set { + snapshot.updateValue(newValue?.snapshot, forKey: "node") + } + } + + public struct Node: GraphQLSelectionSet { + public static let possibleTypes = ["Ref"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField("__typename", type: .nonNull(.scalar(String.self))), + GraphQLField("name", type: .nonNull(.scalar(String.self))), + ] + + public var snapshot: Snapshot + + public init(snapshot: Snapshot) { + self.snapshot = snapshot + } + + public init(name: String) { + self.init(snapshot: ["__typename": "Ref", "name": name]) + } + + public var __typename: String { + get { + return snapshot["__typename"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: "__typename") + } + } + + /// The ref name. + public var name: String { + get { + return snapshot["name"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: "name") + } + } + } + } + } + } + } +} + public final class RepoFileQuery: GraphQLQuery { public static let operationString = "query RepoFile($owner: String!, $name: String!, $branchAndPath: String!) {\n repository(owner: $owner, name: $name) {\n __typename\n object(expression: $branchAndPath) {\n __typename\n ... on Blob {\n text\n }\n }\n }\n}" diff --git a/gql/RepoBranches.graphql b/gql/RepoBranches.graphql new file mode 100644 index 000000000..d109c7764 --- /dev/null +++ b/gql/RepoBranches.graphql @@ -0,0 +1,11 @@ +query fetchRepositoryBranches($owner:String!, $name:String!) { + repository(owner: $owner, name: $name) { + refs(first: 50, refPrefix:"refs/heads/") { + edges { + node { + name + } + } + } + } +}