Skip to content
This repository was archived by the owner on Sep 20, 2023. It is now read-only.

Commit add8bcf

Browse files
authored
Browse commit history of repositories, directories, and files (#2321)
* add history request * Browse commit history of repo, directories, and files
1 parent 397764c commit add8bcf

23 files changed

+1085
-72
lines changed

Classes/History/Client+History.swift

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//
2+
// Client+History.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 10/20/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import GitHubAPI
11+
12+
extension Client {
13+
14+
func fetchHistory(
15+
owner: String,
16+
repo: String,
17+
branch: String,
18+
path: String?,
19+
cursor: String?,
20+
width: CGFloat,
21+
contentSizeCategory: UIContentSizeCategory,
22+
completion: @escaping (Result<([PathCommitModel], String?)>) -> Void
23+
) {
24+
query(
25+
RepoFileHistoryQuery(
26+
owner: owner,
27+
name: repo,
28+
branch: branch,
29+
path: path,
30+
after: cursor,
31+
page_size: 20
32+
),
33+
result: { $0 },
34+
completion: { result in
35+
switch result {
36+
case .failure(let error):
37+
completion(.error(error))
38+
case .success(let data):
39+
let commits = data.commits(width: width, contentSizeCategory: contentSizeCategory)
40+
let nextPage: String?
41+
if let pageInfo = data.repository?.object?.asCommit?.history.pageInfo,
42+
pageInfo.hasNextPage {
43+
nextPage = pageInfo.endCursor
44+
} else {
45+
nextPage = nil
46+
}
47+
completion(.success((commits, nextPage)))
48+
}
49+
})
50+
}
51+
52+
}

Classes/History/PathCommitCell.swift

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// PathCommitCell.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 10/20/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import UIKit
10+
import SnapKit
11+
12+
final class PathCommitCell: SelectableCell {
13+
14+
static let inset = UIEdgeInsets(
15+
top: Styles.Sizes.rowSpacing,
16+
left: Styles.Sizes.gutter,
17+
bottom: Styles.Sizes.rowSpacing,
18+
right: Styles.Sizes.gutter + Styles.Sizes.columnSpacing + Styles.Sizes.icon.width
19+
)
20+
21+
private let textView = MarkdownStyledTextView()
22+
private let disclosureImageView = UIImageView(image: UIImage(named: "chevron-right")?.withRenderingMode(.alwaysTemplate))
23+
24+
override init(frame: CGRect) {
25+
super.init(frame: frame)
26+
27+
backgroundColor = .white
28+
contentView.addSubview(textView)
29+
30+
disclosureImageView.tintColor = Styles.Colors.Gray.light.color
31+
contentView.addSubview(disclosureImageView)
32+
33+
disclosureImageView.snp.makeConstraints { make in
34+
make.right.equalTo(-Styles.Sizes.gutter)
35+
make.centerY.equalToSuperview()
36+
}
37+
38+
addBorder(.bottom, left: Styles.Sizes.gutter)
39+
}
40+
41+
required init?(coder aDecoder: NSCoder) {
42+
fatalError("init(coder:) has not been implemented")
43+
}
44+
45+
override func layoutSubviews() {
46+
super.layoutSubviews()
47+
textView.reposition(for: contentView.bounds.width)
48+
}
49+
50+
// MARK: Public API
51+
52+
func configure(with model: PathCommitModel) {
53+
textView.configure(with: model.text, width: contentView.bounds.width)
54+
}
55+
56+
}

Classes/History/PathCommitModel.swift

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// PathCommitModel.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 10/20/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import IGListKit
11+
import StyledTextKit
12+
13+
struct PathCommitModel: ListSwiftDiffable {
14+
15+
let oid: String
16+
let text: StyledTextRenderer
17+
let commitURL: URL
18+
19+
// MARK: ListSwiftDiffable
20+
21+
var identifier: String {
22+
return oid
23+
}
24+
25+
func isEqual(to value: ListSwiftDiffable) -> Bool {
26+
guard let value = value as? PathCommitModel else { return false }
27+
return text.string == value.text.string
28+
}
29+
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// PathCommitSectionController.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 10/20/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import IGListKit
11+
12+
final class PathCommitSectionController: ListSwiftSectionController<PathCommitModel> {
13+
14+
override func createBinders(from value: PathCommitModel) -> [ListBinder] {
15+
return [
16+
binder(
17+
value,
18+
cellType: ListCellType.class(PathCommitCell.self),
19+
size: {
20+
return CGSize(
21+
width: $0.collection.containerSize.width,
22+
height: $0.value.text.viewSize(in: $0.collection.insetContainerSize.width).height
23+
)
24+
},
25+
configure: {
26+
$0.configure(with: $1.value)
27+
},
28+
didSelect: { [weak self] context in
29+
guard let `self` = self else { return }
30+
context.deselect(animated: true)
31+
self.viewController?.presentSafari(url: context.value.commitURL)
32+
})
33+
]
34+
}
35+
36+
}
37+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// PathHistoryViewController.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 10/20/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import UIKit
10+
import IGListKit
11+
import Squawk
12+
13+
final class PathHistoryViewController: BaseListViewController2<String>,
14+
BaseListViewController2DataSource {
15+
16+
private let viewModel: PathHistoryViewModel
17+
private var models = [PathCommitModel]()
18+
19+
init(viewModel: PathHistoryViewModel) {
20+
self.viewModel = viewModel
21+
super.init(emptyErrorMessage: NSLocalizedString("Cannot load history.", comment: ""))
22+
dataSource = self
23+
}
24+
25+
required init?(coder aDecoder: NSCoder) {
26+
fatalError("init(coder:) has not been implemented")
27+
}
28+
29+
override func viewDidLoad() {
30+
super.viewDidLoad()
31+
32+
let titleView = NavigationTitleDropdownView(chevronVisible: false)
33+
titleView.configure(
34+
title: NSLocalizedString("History", comment: ""),
35+
subtitle: viewModel.path?.path
36+
)
37+
navigationItem.titleView = titleView
38+
}
39+
40+
override func fetch(page: String?) {
41+
// assumptions here, but the collectionview may not have been laid out or content size found
42+
// assume the collectionview is pinned to the view's bounds
43+
let contentInset = feed.collectionView.contentInset
44+
let width = view.bounds.width - contentInset.left - contentInset.right
45+
46+
viewModel.client.client.fetchHistory(
47+
owner: viewModel.owner,
48+
repo: viewModel.repo,
49+
branch: viewModel.branch,
50+
path: viewModel.path?.path,
51+
cursor: page,
52+
width: width,
53+
contentSizeCategory: UIApplication.shared.preferredContentSizeCategory
54+
) { [weak self] result in
55+
switch result {
56+
case .error(let error):
57+
Squawk.show(error: error)
58+
case .success(let commits, let nextPage):
59+
if page == nil {
60+
self?.models = commits
61+
} else {
62+
self?.models += commits
63+
}
64+
self?.update(page: nextPage, animated: trueUnlessReduceMotionEnabled)
65+
}
66+
}
67+
}
68+
69+
// MARK: BaseListViewController2DataSource
70+
71+
func models(adapter: ListSwiftAdapter) -> [ListSwiftPair] {
72+
return models.map { ListSwiftPair.pair($0, { PathCommitSectionController() }) }
73+
}
74+
75+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// PathHistoryViewModel.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 10/20/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
struct PathHistoryViewModel {
12+
13+
let owner: String
14+
let repo: String
15+
let client: GithubClient
16+
let branch: String
17+
let path: FilePath?
18+
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// HistoryGraphQLToPathCommitModel.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 10/20/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import StyledTextKit
11+
12+
extension RepoFileHistoryQuery.Data {
13+
14+
func commits(
15+
width: CGFloat,
16+
contentSizeCategory: UIContentSizeCategory
17+
) -> [PathCommitModel] {
18+
guard let nodes = repository?.object?.asCommit?.history.nodes
19+
else { return [] }
20+
21+
return nodes.compactMap {
22+
guard let model = $0,
23+
let author = model.author?.user?.login,
24+
let date = model.committedDate.githubDate,
25+
let url = URL(string: model.url)
26+
else { return nil }
27+
28+
let paragraphStyle = NSMutableParagraphStyle()
29+
paragraphStyle.paragraphSpacing = 12
30+
paragraphStyle.lineSpacing = 2
31+
let attributes: [NSAttributedStringKey: Any] = [
32+
.foregroundColor: Styles.Colors.Gray.dark.color,
33+
.paragraphStyle: paragraphStyle,
34+
.backgroundColor: UIColor.white
35+
]
36+
37+
let builder = StyledTextBuilder(styledText: StyledText(
38+
style: Styles.Text.bodyBold.with(attributes: attributes)
39+
))
40+
.add(text: "\(model.message.firstLine)\n")
41+
.add(style: Styles.Text.secondary.with(foreground: Styles.Colors.Gray.medium.color))
42+
.save()
43+
.add(text: author, traits: [.traitBold])
44+
.restore()
45+
46+
if let committer = model.committer?.user?.login {
47+
builder.add(text: NSLocalizedString(" authored and ", comment: ""))
48+
.save()
49+
.add(text: committer, traits: [.traitBold])
50+
.restore()
51+
}
52+
53+
builder.add(text: NSLocalizedString(" committed ", comment: ""))
54+
.save()
55+
.add(styledText: StyledText(
56+
text: model.oid.hashDisplay,
57+
style: Styles.Text.secondaryCodeBold.with(foreground: Styles.Colors.Blue.medium.color)
58+
))
59+
.restore()
60+
.add(text: " \(date.agoString(.long))")
61+
62+
return PathCommitModel(
63+
oid: model.oid,
64+
text: StyledTextRenderer(
65+
string: builder.build(),
66+
contentSizeCategory: contentSizeCategory,
67+
inset: PathCommitCell.inset,
68+
backgroundColor: .white
69+
).warm(width: width),
70+
commitURL: url
71+
)
72+
}
73+
}
74+
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// UIViewController+HistoryAction.swift
3+
// Freetime
4+
//
5+
// Created by Ryan Nystrom on 10/20/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
extension UIViewController {
12+
func viewHistoryAction(
13+
owner: String,
14+
repo: String,
15+
branch: String,
16+
client: GithubClient,
17+
path: FilePath? = nil
18+
) -> UIAlertAction {
19+
return UIAlertAction(
20+
title: NSLocalizedString("View History", comment: ""),
21+
style: .default
22+
) { [weak self] _ in
23+
self?.navigationController?.pushViewController(
24+
PathHistoryViewController(
25+
viewModel: PathHistoryViewModel(
26+
owner: owner,
27+
repo: repo,
28+
client: client,
29+
branch: branch,
30+
path: path
31+
)
32+
),
33+
animated: trueUnlessReduceMotionEnabled
34+
)
35+
}
36+
}
37+
}

Classes/Issues/IssuesViewController.swift

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import UIKit
1010
import IGListKit
1111
import TUSafariActivity
12-
import SafariServices
1312
import SnapKit
1413
import FlatCache
1514
import MessageViewController

0 commit comments

Comments
 (0)