simple readme support in repos

This commit is contained in:
Ryan Nystrom
2017-09-10 21:08:59 -04:00
parent d6219f05e3
commit 17fb9ae7db
7 changed files with 284 additions and 43 deletions

View File

@@ -198,5 +198,22 @@ final class RepositoryClient {
)))
}
}
func fetchReadme(
completion: @escaping (Result<String>) -> ()
) {
githubClient.request(GithubClient.Request(
path: "repos/\(owner)/\(name)/readme",
completion: { (response, _) in
if let json = response.value as? [String: Any],
let content = json["content"] as? String,
let data = Data(base64Encoded: content, options: [.ignoreUnknownCharacters]),
let text = String(data: data, encoding: .utf8) {
completion(.success(text))
} else {
completion(.error(response.error))
}
}))
}
}

View File

@@ -0,0 +1,120 @@
//
// RepositoryDataSource.swift
// Freetime
//
// Created by Ryan Nystrom on 9/10/17.
// Copyright © 2017 Ryan Nystrom. All rights reserved.
//
import UIKit
import IGListKit
final class RepositoryDataSource {
static let readmeTitle = NSLocalizedString("Readme", comment: "")
static let issuesTitle = NSLocalizedString("Issues", comment: "")
static let pullRequestsTitle = NSLocalizedString("Pull Requests", comment: "")
// mutated by SegmentedControlSectionController
let selection: SegmentedControlModel
let loadMore = "loadMore" as ListDiffable
private var readmeModel: RepositoryReadmeModel? = nil
private var issues = [RepositoryIssueSummaryModel]()
private var pullRequests = [RepositoryIssueSummaryModel]()
// public for paging
private(set) var issuesNextPage: String?
private(set) var pullRequestsNextPage: String?
init(hasIssuesEnabled: Bool) {
var items = [RepositoryDataSource.readmeTitle]
if hasIssuesEnabled {
items.append(RepositoryDataSource.issuesTitle)
}
items.append(RepositoryDataSource.pullRequestsTitle)
selection = SegmentedControlModel(items: items)
}
// MARK: Public
enum State {
case readme
case issues
case pullRequests
}
var state: State {
switch selection.items[selection.selectedIndex] {
case RepositoryDataSource.readmeTitle: return .readme
case RepositoryDataSource.issuesTitle: return .issues
case RepositoryDataSource.pullRequestsTitle: return .pullRequests
default: fatalError("Repository selection in unknown state")
}
}
var selectionModels: [ListDiffable] {
switch state {
case .readme:
if let model = readmeModel {
return [model]
}
return []
case .issues:
var models: [ListDiffable] = issues
if issuesNextPage != nil {
models.append(loadMore)
}
return models
case .pullRequests:
var models: [ListDiffable] = pullRequests
if pullRequestsNextPage != nil {
models.append(loadMore)
}
return models
}
}
func reset(
issues: [RepositoryIssueSummaryModel],
issuesNextPage: String?,
pullRequests: [RepositoryIssueSummaryModel],
pullRequestsNextPage: String?
) {
self.issues = issues
self.issuesNextPage = issuesNextPage
self.pullRequests = pullRequests
self.pullRequestsNextPage = pullRequestsNextPage
}
func setReadme(
_ readme: String,
width: CGFloat,
completion: @escaping () -> ()
) {
DispatchQueue.global().async {
let models = CreateCommentModels(markdown: readme, width: width)
let model = RepositoryReadmeModel(models: models)
DispatchQueue.main.async {
self.readmeModel = model
completion()
}
}
}
func setPullRequests(_ pullRequests: [RepositoryIssueSummaryModel], page: String?) {
self.pullRequests = pullRequests
self.pullRequestsNextPage = page
}
func appendIssues(issues: [RepositoryIssueSummaryModel], page: String?) {
self.issues += issues
self.issuesNextPage = page
}
func appendPullRequests(pullRequests: [RepositoryIssueSummaryModel], page: String?) {
self.pullRequests += pullRequests
self.pullRequestsNextPage = page
}
}

View File

@@ -0,0 +1,31 @@
//
// RepositoryReadmeModel.swift
// Freetime
//
// Created by Ryan Nystrom on 9/10/17.
// Copyright © 2017 Ryan Nystrom. All rights reserved.
//
import Foundation
import IGListKit
final class RepositoryReadmeModel: ListDiffable {
let models: [ListDiffable]
init(models: [ListDiffable]) {
self.models = models
}
// MARK: ListDiffable
func diffIdentifier() -> NSObjectProtocol {
return "readme" as NSObjectProtocol
}
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
// for use w/ binding SC
return true
}
}

View File

@@ -0,0 +1,64 @@
//
// RepositoryReadmeSectionController.swift
// Freetime
//
// Created by Ryan Nystrom on 9/10/17.
// Copyright © 2017 Ryan Nystrom. All rights reserved.
//
import Foundation
import IGListKit
final class RepositoryReadmeSectionController: ListBindingSectionController<RepositoryReadmeModel>,
ListBindingSectionControllerDataSource {
private lazy var webviewCache: WebviewCellHeightCache = {
return WebviewCellHeightCache(sectionController: self)
}()
override init() {
super.init()
dataSource = self
}
// MARK: ListBindingSectionControllerDataSource
func sectionController(
_ sectionController: ListBindingSectionController<ListDiffable>,
viewModelsFor object: Any
) -> [ListDiffable] {
return self.object?.models ?? []
}
func sectionController(
_ sectionController: ListBindingSectionController<ListDiffable>,
sizeForViewModel viewModel: Any,
at index: Int
) -> CGSize {
guard let width = collectionContext?.containerSize.width else { fatalError("Missing context") }
let height = BodyHeightForComment(viewModel: viewModel, width: width, webviewCache: webviewCache)
return CGSize(width: width, height: height)
}
func sectionController(
_ sectionController: ListBindingSectionController<ListDiffable>,
cellForViewModel viewModel: Any,
at index: Int
) -> UICollectionViewCell {
guard let context = self.collectionContext else { fatalError("Missing context") }
let cellClass: AnyClass = CellTypeForComment(viewModel: viewModel)
let cell = context.dequeueReusableCell(of: cellClass, for: self, at: index)
ExtraCommentCellConfigure(
cell: cell,
imageDelegate: nil,
htmlDelegate: webviewCache,
htmlNavigationDelegate: nil,
attributedDelegate: nil
)
return cell
}
}

View File

@@ -25,21 +25,16 @@ PrimaryViewController {
private let repo: RepositoryDetails
private let client: RepositoryClient
private lazy var feed: Feed = { Feed(viewController: self, delegate: self) }()
private let selection: SegmentedControlModel
private let noIssuesResultsKey = "noIssuesResultsKey" as ListDiffable
private let noPullRequestsResultsKey = "noPullRequestsResultsKey" as ListDiffable
private let loadMore = "loadMore" as ListDiffable
private var issues = [RepositoryIssueSummaryModel]()
private var issuesNextPage: String?
private var pullRequests = [RepositoryIssueSummaryModel]()
private var pullRequestsNextPage: String?
private let dataSource: RepositoryDataSource
init(client: GithubClient, repo: RepositoryDetails) {
self.repo = repo
self.client = RepositoryClient(githubClient: client, owner: repo.owner, name: repo.name)
self.selection = SegmentedControlModel.forRepository(repo.hasIssuesEnabled)
self.dataSource = RepositoryDataSource(hasIssuesEnabled: repo.hasIssuesEnabled)
super.init(nibName: nil, bundle: nil)
}
@@ -72,14 +67,26 @@ PrimaryViewController {
}
func reload() {
client.fetchReadme { result in
switch result {
case .error: break
case .success(let readme):
self.dataSource.setReadme(readme, width: self.view.bounds.width, 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.issues = payload.issues.models
self.issuesNextPage = payload.issues.nextPage
self.pullRequests = payload.pullRequests.models
self.pullRequestsNextPage = payload.pullRequests.nextPage
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)
}
}
@@ -88,23 +95,23 @@ PrimaryViewController {
func loadNextPage() {
let width = view.bounds.width
if selection.issuesSelected {
client.loadMoreIssues(nextPage: issuesNextPage, containerWidth: width, completion: { result in
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.issues += payload.models
self.issuesNextPage = payload.nextPage
self.dataSource.appendIssues(issues: payload.models, page: payload.nextPage)
self.update(dismissRefresh: true, animated: false)
}
})
} else {
client.loadMorePullRequests(nextPage: issuesNextPage, containerWidth: width, completion: { result in
case .pullRequests:
client.loadMorePullRequests(nextPage: dataSource.pullRequestsNextPage, containerWidth: width, completion: { result in
switch result {
case .error: break
case .success(let payload):
self.pullRequests += payload.models
self.pullRequestsNextPage = payload.nextPage
self.dataSource.appendPullRequests(pullRequests: payload.models, page: payload.nextPage)
self.update(dismissRefresh: true, animated: false)
}
})
@@ -125,28 +132,16 @@ PrimaryViewController {
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var builder = [ListDiffable]()
builder.append(dataSource.selection)
if repo.hasIssuesEnabled {
builder.append(selection)
}
let models = dataSource.selectionModels
builder += models
if selection.issuesSelected {
if issues.count > 0 {
builder += issues as [ListDiffable]
if issuesNextPage != nil {
builder.append(loadMore)
}
} else if feed.status == .idle {
builder.append(noIssuesResultsKey)
}
} else if !selection.issuesSelected {
if pullRequests.count > 0 {
builder += pullRequests as [ListDiffable]
if pullRequestsNextPage != nil {
builder.append(loadMore)
}
} else if feed.status == .idle {
builder.append(noPullRequestsResultsKey)
if models.count == 0, feed.status == .idle {
switch dataSource.state {
case .readme: break
case .issues: builder.append(noIssuesResultsKey)
case .pullRequests: builder.append(noPullRequestsResultsKey)
}
}
@@ -163,12 +158,14 @@ PrimaryViewController {
return RepositoryEmptyResultsSectionController(topInset: controlHeight, topLayoutGuide: topLayoutGuide, type: .issues)
} else if object === noPullRequestsResultsKey {
return RepositoryEmptyResultsSectionController(topInset: controlHeight, topLayoutGuide: topLayoutGuide, type: .pullRequests)
} else if object === selection {
} else if object === dataSource.selection {
return SegmentedControlSectionController(delegate: self, height: controlHeight)
} else if object === loadMore {
} 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")

View File

@@ -290,6 +290,9 @@
29EDFE7C1F65C580005BCCEB /* SplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EDFE7B1F65C580005BCCEB /* SplitViewTests.swift */; };
29EDFE7D1F65C583005BCCEB /* SplitViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AC90E41F00A7C8000B80E4 /* SplitViewControllerDelegate.swift */; };
29EDFE7E1F65C688005BCCEB /* SplitPlaceholderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C33FDA1F127DBB00EC8D40 /* SplitPlaceholderViewController.swift */; };
29EDFE801F6606B6005BCCEB /* RepositoryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EDFE7F1F6606B6005BCCEB /* RepositoryDataSource.swift */; };
29EDFE821F661562005BCCEB /* RepositoryReadmeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EDFE811F661562005BCCEB /* RepositoryReadmeModel.swift */; };
29EDFE841F661776005BCCEB /* RepositoryReadmeSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EDFE831F661776005BCCEB /* RepositoryReadmeSectionController.swift */; };
29EE1C121F37C51D0046A54D /* IssueLabelEditCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EE1C111F37C51D0046A54D /* IssueLabelEditCell.swift */; };
29EE1C171F3A2E7A0046A54D /* Labels.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 29EE1C161F3A2E7A0046A54D /* Labels.storyboard */; };
29EE1C191F3A2EDB0046A54D /* LabelTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EE1C181F3A2EDB0046A54D /* LabelTableCell.swift */; };
@@ -611,6 +614,9 @@
29DA1E8B1F5F8CC40050C64B /* MarkdownAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownAttribute.swift; sourceTree = "<group>"; };
29EB1EEE1F425E5100A200B4 /* ForegroundHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForegroundHandler.swift; sourceTree = "<group>"; };
29EDFE7B1F65C580005BCCEB /* SplitViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplitViewTests.swift; sourceTree = "<group>"; };
29EDFE7F1F6606B6005BCCEB /* RepositoryDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryDataSource.swift; sourceTree = "<group>"; };
29EDFE811F661562005BCCEB /* RepositoryReadmeModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryReadmeModel.swift; sourceTree = "<group>"; };
29EDFE831F661776005BCCEB /* RepositoryReadmeSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryReadmeSectionController.swift; sourceTree = "<group>"; };
29EE1C111F37C51D0046A54D /* IssueLabelEditCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueLabelEditCell.swift; sourceTree = "<group>"; };
29EE1C161F3A2E7A0046A54D /* Labels.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Labels.storyboard; sourceTree = "<group>"; };
29EE1C181F3A2EDB0046A54D /* LabelTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelTableCell.swift; sourceTree = "<group>"; };
@@ -1418,9 +1424,12 @@
children = (
292ACE191F5CAF9F00C9A02C /* Empty */,
986B873B1F2CEB1500AAB55C /* GQL+RepositoryIssueSummaryType.swift */,
986B873D1F2E1CE400AAB55C /* RepositoryClient.swift */,
29EDFE7F1F6606B6005BCCEB /* RepositoryDataSource.swift */,
292ACE171F5C945B00C9A02C /* RepositoryIssueSummaryModel.swift */,
986B87331F2CAE9800AAB55C /* RepositoryIssueSummaryType.swift */,
986B873D1F2E1CE400AAB55C /* RepositoryClient.swift */,
29EDFE811F661562005BCCEB /* RepositoryReadmeModel.swift */,
29EDFE831F661776005BCCEB /* RepositoryReadmeSectionController.swift */,
986B87371F2CB29700AAB55C /* RepositorySummaryCell.swift */,
986B87351F2CB28C00AAB55C /* RepositorySummarySectionController.swift */,
986B872F1F2CA90400AAB55C /* RepositoryViewController.swift */,
@@ -1717,6 +1726,7 @@
292FCAF81EDFCC510026635E /* CollapsibleCell.swift in Sources */,
290744BA1F26863100FD9E48 /* UIScrollView+ScrollToBottom.swift in Sources */,
29DA1E8C1F5F8CC40050C64B /* MarkdownAttribute.swift in Sources */,
29EDFE841F661776005BCCEB /* RepositoryReadmeSectionController.swift in Sources */,
29DA1E7F1F5DF2960050C64B /* LoadMoreCell.swift in Sources */,
29DA1E791F5DEE8F0050C64B /* SearchLoadingView.swift in Sources */,
29CD71171EF22F4200616E85 /* NSAttributedString+Trim.swift in Sources */,
@@ -1746,6 +1756,7 @@
292FCB071EDFCC510026635E /* IssueCommentReactionCell.swift in Sources */,
29316DCD1ECD31E9007CAE3F /* StatusBar.swift in Sources */,
29F7F05C1F2A751B00F6075D /* IssueResult.swift in Sources */,
29EDFE801F6606B6005BCCEB /* RepositoryDataSource.swift in Sources */,
2958406D1EE8EBF3007723C6 /* IssueCommentPhoto.swift in Sources */,
290EF56A1F06A821006A2160 /* Notification+NotificationViewModel.swift in Sources */,
986B87301F2CA90400AAB55C /* RepositoryViewController.swift in Sources */,
@@ -1901,6 +1912,7 @@
2963A9341EE2118E0066509C /* ResponderButton.swift in Sources */,
2981A8A41EFE9FC700E25EF1 /* GithubEmoji.swift in Sources */,
29C9FDD81EC65FEE00EE3A52 /* User.swift in Sources */,
29EDFE821F661562005BCCEB /* RepositoryReadmeModel.swift in Sources */,
292FCB0A1EDFCC510026635E /* IssueCommentTextCell.swift in Sources */,
299C06DA1F0DD87F00C2828E /* GithubClient+Paging.swift in Sources */,
2928C78E1F15DF1B0000D06D /* IssueRenamedString.swift in Sources */,

View File

@@ -32,7 +32,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1748</string>
<string>1761</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>