mirror of
https://github.com/zhigang1992/GitHawk.git
synced 2026-04-29 12:35:00 +08:00
Reply to Pull Request Review comments (#1483)
* wip * reply cell shows up in comments * focus on reply model and scroll to reply * api to create PR comment replies * reply working * clean and code reuse * even more reuse * organize
This commit is contained in:
@@ -13,16 +13,18 @@ final class IssueDiffHunkModel: ListDiffable {
|
||||
|
||||
let path: String
|
||||
let preview: NSAttributedStringSizing
|
||||
let offset: Int
|
||||
|
||||
init(path: String, preview: NSAttributedStringSizing) {
|
||||
init(path: String, preview: NSAttributedStringSizing, offset: Int) {
|
||||
self.path = path
|
||||
self.preview = preview
|
||||
self.offset = offset
|
||||
}
|
||||
|
||||
// MARK: ListDiffable
|
||||
|
||||
func diffIdentifier() -> NSObjectProtocol {
|
||||
return preview.attributedText.string as NSObjectProtocol
|
||||
return "\(preview.attributedText.string)-\(offset)" as NSObjectProtocol
|
||||
}
|
||||
|
||||
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
|
||||
|
||||
@@ -136,20 +136,7 @@ IssueManagingNavSectionControllerDelegate {
|
||||
view.backgroundColor = Styles.Colors.background
|
||||
|
||||
// setup message view properties
|
||||
borderColor = Styles.Colors.Gray.border.color
|
||||
messageView.textView.placeholderText = NSLocalizedString("Leave a comment", comment: "")
|
||||
messageView.textView.placeholderTextColor = Styles.Colors.Gray.light.color
|
||||
messageView.keyboardType = .twitter
|
||||
messageView.set(buttonIcon: UIImage(named: "send")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
messageView.buttonTint = Styles.Colors.Blue.medium.color
|
||||
messageView.font = Styles.Fonts.body
|
||||
messageView.inset = UIEdgeInsets(
|
||||
top: Styles.Sizes.gutter,
|
||||
left: Styles.Sizes.gutter,
|
||||
bottom: Styles.Sizes.rowSpacing / 2,
|
||||
right: Styles.Sizes.gutter
|
||||
)
|
||||
messageView.addButton(target: self, action: #selector(didPressButton(_:)))
|
||||
configure(target: self, action: #selector(didPressButton(_:)))
|
||||
|
||||
let getMarkdownBlock = { [weak self] () -> (String) in
|
||||
return self?.messageView.text ?? ""
|
||||
@@ -332,31 +319,12 @@ IssueManagingNavSectionControllerDelegate {
|
||||
|
||||
func scrollToLastContentElement() {
|
||||
let adapter = feed.adapter
|
||||
let collectionView = feed.collectionView
|
||||
let objects = adapter.objects()
|
||||
guard objects.count > 1 else { return }
|
||||
|
||||
// assuming the last element is the "actions" when collaborator
|
||||
let lastContent = objects[objects.count - (viewerIsCollaborator ? 2 : 1)]
|
||||
|
||||
guard let sectionController = adapter.sectionController(for: lastContent) else { return }
|
||||
|
||||
let lastItemIndex = sectionController.numberOfItems() - 1
|
||||
let path = IndexPath(item: lastItemIndex, section: sectionController.section)
|
||||
|
||||
guard let attributes = feed.collectionView.layoutAttributesForItem(at: path) else { return }
|
||||
|
||||
let paddedMaxY = min(attributes.frame.maxY + Styles.Sizes.rowSpacing, collectionView.contentSize.height)
|
||||
let viewportHeight = collectionView.bounds.height
|
||||
|
||||
// make sure not already at the top
|
||||
guard paddedMaxY > viewportHeight else { return }
|
||||
|
||||
let offset = paddedMaxY - viewportHeight
|
||||
collectionView.setContentOffset(
|
||||
CGPoint(x: collectionView.contentOffset.x, y: offset),
|
||||
animated: trueUnlessReduceMotionEnabled
|
||||
)
|
||||
adapter.scroll(to: lastContent, padding: Styles.Sizes.rowSpacing)
|
||||
}
|
||||
|
||||
func onPreview() {
|
||||
|
||||
@@ -383,7 +383,7 @@ extension IssueOrPullRequestQuery.Data.Repository.IssueOrPullRequest.AsPullReque
|
||||
guard let node = thread.comments.nodes?.first, let firstComment = node else { return nil }
|
||||
let code = CreateDiffString(code: firstComment.diffHunk, limit: true)
|
||||
let text = NSAttributedStringSizing(containerWidth: 0, attributedText: code, inset: IssueDiffHunkPreviewCell.textViewInset)
|
||||
return IssueDiffHunkModel(path: firstComment.path, preview: text)
|
||||
return IssueDiffHunkModel(path: firstComment.path, preview: text, offset: 0)
|
||||
}
|
||||
|
||||
private func commentModels(
|
||||
|
||||
@@ -83,18 +83,19 @@ extension GithubClient {
|
||||
attributedText: code,
|
||||
inset: IssueDiffHunkPreviewCell.textViewInset
|
||||
)
|
||||
models.append(IssueDiffHunkModel(path: thread.path, preview: text))
|
||||
models.append(IssueDiffHunkModel(path: thread.path, preview: text, offset: models.count))
|
||||
|
||||
for (i, comment) in thread.comments.enumerated() {
|
||||
for comment in thread.comments {
|
||||
models.append(createReviewComment(
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
model: comment,
|
||||
viewer: viewerUsername,
|
||||
width: width,
|
||||
isLast: i == thread.comments.count - 1
|
||||
width: width
|
||||
))
|
||||
}
|
||||
|
||||
models.append(PullRequestReviewReplyModel(replyID: id))
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
@@ -104,6 +105,42 @@ extension GithubClient {
|
||||
}))
|
||||
}
|
||||
|
||||
func sendComment(
|
||||
body: String,
|
||||
inReplyTo: Int,
|
||||
owner: String,
|
||||
repo: String,
|
||||
number: Int,
|
||||
width: CGFloat,
|
||||
completion: @escaping (Result<IssueCommentModel>) -> ()
|
||||
) {
|
||||
let viewer = userSession?.username
|
||||
|
||||
// https://developer.github.com/v3/pulls/comments/#alternative-input
|
||||
request(Request(
|
||||
path: "repos/\(owner)/\(repo)/pulls/\(number)/comments",
|
||||
method: .post,
|
||||
parameters: ["body": body, "in_reply_to": inReplyTo],
|
||||
completion: { (response, _) in
|
||||
if response.response?.statusCode == 201,
|
||||
let json = response.value as? [String: Any],
|
||||
let id = json["id"] as? Int,
|
||||
let model = createReviewCommentModel(id: id, json: json) {
|
||||
let comment = createReviewComment(
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
model: model,
|
||||
viewer: viewer,
|
||||
width: width
|
||||
)
|
||||
completion(.success(comment))
|
||||
} else {
|
||||
ToastManager.showGenericError()
|
||||
completion(.error(response.error))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func createReviewCommentModel(id: Int, json: [String: Any]) -> GithubClient.ReviewComment? {
|
||||
@@ -128,8 +165,7 @@ private func createReviewComment(
|
||||
repo: String,
|
||||
model: GithubClient.ReviewComment,
|
||||
viewer: String?,
|
||||
width: CGFloat,
|
||||
isLast: Bool
|
||||
width: CGFloat
|
||||
) -> IssueCommentModel {
|
||||
let details = IssueCommentDetailsViewModel(
|
||||
date: model.created,
|
||||
@@ -150,7 +186,7 @@ private func createReviewComment(
|
||||
bodyModels: bodies,
|
||||
reactions: reactions,
|
||||
collapse: nil,
|
||||
threadState: isLast ? .tail : .neck,
|
||||
threadState: .neck,
|
||||
rawMarkdown: model.body,
|
||||
viewerCanUpdate: false,
|
||||
viewerCanDelete: false,
|
||||
|
||||
@@ -8,24 +8,34 @@
|
||||
|
||||
import UIKit
|
||||
import IGListKit
|
||||
import MessageViewController
|
||||
|
||||
final class PullRequestReviewCommentsViewController: BaseListViewController<NSNumber>,
|
||||
BaseListViewControllerDataSource {
|
||||
final class PullRequestReviewCommentsViewController: MessageViewController,
|
||||
ListAdapterDataSource,
|
||||
FeedDelegate,
|
||||
PullRequestReviewReplySectionControllerDelegate {
|
||||
|
||||
private let model: IssueDetailsModel
|
||||
private let client: GithubClient
|
||||
private let autocomplete: IssueCommentAutocomplete
|
||||
private var models = [ListDiffable]()
|
||||
private var results = [ListDiffable]()
|
||||
private let textActionsController = TextActionsController()
|
||||
private var autocompleteController: AutocompleteController!
|
||||
private var focusedReplyModel: PullRequestReviewReplyModel?
|
||||
|
||||
lazy private var feed: Feed = {
|
||||
let f = Feed(viewController: self, delegate: self, managesLayout: false)
|
||||
f.collectionView.contentInset = Styles.Sizes.threadInset
|
||||
return f
|
||||
}()
|
||||
|
||||
init(model: IssueDetailsModel, client: GithubClient, autocomplete: IssueCommentAutocomplete) {
|
||||
self.model = model
|
||||
self.client = client
|
||||
self.autocomplete = autocomplete
|
||||
super.init(
|
||||
emptyErrorMessage: NSLocalizedString("Error loading review comments.", comment: ""),
|
||||
dataSource: self
|
||||
)
|
||||
feed.collectionView.contentInset = Styles.Sizes.threadInset
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
title = NSLocalizedString("Review Comments", comment: "")
|
||||
}
|
||||
|
||||
@@ -33,6 +43,48 @@ BaseListViewControllerDataSource {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
feed.viewDidLoad()
|
||||
feed.adapter.dataSource = self
|
||||
|
||||
// setup after feed is lazy loaded
|
||||
setup(scrollView: feed.collectionView)
|
||||
setMessageView(hidden: true, animated: false)
|
||||
|
||||
// override Feed bg color setting
|
||||
view.backgroundColor = Styles.Colors.background
|
||||
|
||||
// setup message view properties
|
||||
configure(target: self, action: #selector(didPressButton(_:)))
|
||||
|
||||
let getMarkdownBlock = { [weak self] () -> (String) in
|
||||
return self?.messageView.text ?? ""
|
||||
}
|
||||
let actions = IssueTextActionsView.forMarkdown(
|
||||
viewController: self,
|
||||
getMarkdownBlock: getMarkdownBlock,
|
||||
repo: model.repo,
|
||||
owner: model.owner,
|
||||
addBorder: false,
|
||||
supportsImageUpload: true
|
||||
)
|
||||
// text input bar uses UIVisualEffectView, don't try to match it
|
||||
actions.backgroundColor = .clear
|
||||
|
||||
textActionsController.configure(client: client, textView: messageView.textView, actions: actions)
|
||||
textActionsController.viewController = self
|
||||
|
||||
actions.frame = CGRect(x: 0, y: 0, width: 0, height: 40)
|
||||
messageView.add(contentView: actions)
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
feed.viewWillLayoutSubviews(view: view)
|
||||
}
|
||||
|
||||
override func viewSafeAreaInsetsDidChange() {
|
||||
if #available(iOS 11.0, *) {
|
||||
super.viewSafeAreaInsetsDidChange()
|
||||
@@ -40,36 +92,79 @@ BaseListViewControllerDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Overrides
|
||||
// MARK: FeedDelegate
|
||||
|
||||
override func fetch(page: NSNumber?) {
|
||||
func loadFromNetwork(feed: Feed) {
|
||||
fetch()
|
||||
}
|
||||
|
||||
func loadNextPage(feed: Feed) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: Private API
|
||||
|
||||
var insetWidth: CGFloat {
|
||||
let contentInset = feed.collectionView.contentInset
|
||||
return view.bounds.width - contentInset.left - contentInset.right
|
||||
}
|
||||
|
||||
func fetch() {
|
||||
client.fetchPRComments(
|
||||
owner: model.owner,
|
||||
repo: model.repo,
|
||||
number: model.number,
|
||||
width: view.bounds.width
|
||||
width: insetWidth
|
||||
) { [weak self] (result) in
|
||||
switch result {
|
||||
case .error: ToastManager.showGenericError()
|
||||
case .success(let models, let page):
|
||||
self?.models = models
|
||||
self?.update(page: page as NSNumber?, animated: trueUnlessReduceMotionEnabled)
|
||||
case .success(let models, _):
|
||||
self?.results = models
|
||||
self?.feed.finishLoading(dismissRefresh: true, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: BaseListViewControllerDataSource
|
||||
@objc func didPressButton(_ sender: Any) {
|
||||
guard let reply = focusedReplyModel else { return }
|
||||
|
||||
let text = messageView.text
|
||||
messageView.text = ""
|
||||
messageView.textView.resignFirstResponder()
|
||||
setMessageView(hidden: true, animated: true)
|
||||
|
||||
client.sendComment(
|
||||
body: text,
|
||||
inReplyTo: reply.replyID,
|
||||
owner: model.owner,
|
||||
repo: model.repo,
|
||||
number: model.number,
|
||||
width: insetWidth
|
||||
) { [weak self] result in
|
||||
switch result {
|
||||
case .error: break
|
||||
case .success(let comment): self?.insertComment(model: comment, reply: reply)
|
||||
}
|
||||
}
|
||||
|
||||
func headModels(listAdapter: ListAdapter) -> [ListDiffable] {
|
||||
return []
|
||||
}
|
||||
|
||||
func models(listAdapter: ListAdapter) -> [ListDiffable] {
|
||||
return models
|
||||
func insertComment(model: IssueCommentModel, reply: PullRequestReviewReplyModel) {
|
||||
let section = feed.adapter.section(for: reply)
|
||||
guard section < NSNotFound && section < results.count else { return }
|
||||
|
||||
results.insert(model, at: section)
|
||||
feed.adapter.performUpdates(animated: true)
|
||||
}
|
||||
|
||||
func sectionController(model: Any, listAdapter: ListAdapter) -> ListSectionController {
|
||||
switch model {
|
||||
// MARK: ListAdapterDataSource
|
||||
|
||||
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
|
||||
return results
|
||||
}
|
||||
|
||||
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
|
||||
switch object {
|
||||
case is NSAttributedStringSizing: return IssueTitleSectionController()
|
||||
case is IssueCommentModel: return IssueCommentSectionController(
|
||||
model: self.model,
|
||||
@@ -77,25 +172,31 @@ BaseListViewControllerDataSource {
|
||||
autocomplete: autocomplete
|
||||
)
|
||||
case is IssueDiffHunkModel: return IssueDiffHunkSectionController()
|
||||
case is PullRequestReviewReplyModel: return PullRequestReviewReplySectionController(delegate: self)
|
||||
// add case for reply model + SC. connect SC.delegate = self
|
||||
default: fatalError("Unhandled object: \(model)")
|
||||
}
|
||||
}
|
||||
|
||||
func emptySectionController(listAdapter: ListAdapter) -> ListSectionController {
|
||||
return ListSingleSectionController(cellClass: LabelCell.self, configureBlock: { (_, cell: UICollectionViewCell) in
|
||||
guard let cell = cell as? LabelCell else { return }
|
||||
cell.label.text = NSLocalizedString("No review comments found.", comment: "")
|
||||
}, sizeBlock: { [weak self] (_, context: ListCollectionContext?) -> CGSize in
|
||||
guard let context = context,
|
||||
let strongSelf = self
|
||||
else { return .zero }
|
||||
return CGSize(
|
||||
width: context.containerSize.width,
|
||||
height: context.containerSize.height - strongSelf.topLayoutGuide.length - strongSelf.bottomLayoutGuide.length
|
||||
)
|
||||
})
|
||||
func emptyView(for listAdapter: ListAdapter) -> UIView? {
|
||||
switch feed.status {
|
||||
case .idle:
|
||||
let emptyView = EmptyView()
|
||||
emptyView.label.text = NSLocalizedString("Error loading review comments.", comment: "")
|
||||
return emptyView
|
||||
case .loading, .loadingNext:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: IssueCommentSectionControllerDelegate
|
||||
// MARK: PullRequestReviewReplySectionControllerDelegate
|
||||
|
||||
func didSelect(replySectionController: PullRequestReviewReplySectionController, reply: PullRequestReviewReplyModel) {
|
||||
setMessageView(hidden: false, animated: true)
|
||||
messageView.textView.becomeFirstResponder()
|
||||
feed.adapter.scroll(to: reply, padding: Styles.Sizes.rowSpacing)
|
||||
|
||||
focusedReplyModel = reply
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
44
Classes/PullRequestReviews/PullRequestReviewReplyCell.swift
Normal file
44
Classes/PullRequestReviews/PullRequestReviewReplyCell.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// PullRequestReviewReplyCell.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 1/29/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
final class PullRequestReviewReplyCell: IssueCommentBaseCell {
|
||||
|
||||
private let button = UIButton()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
let color = Styles.Colors.Blue.medium.color
|
||||
button.setTitle(NSLocalizedString("Reply", comment: ""), for: .normal)
|
||||
button.setImage(UIImage(named: "reply")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
button.tintColor = color
|
||||
button.setTitleColor(color, for: .normal)
|
||||
button.titleLabel?.font = Styles.Fonts.body
|
||||
button.isUserInteractionEnabled = false
|
||||
|
||||
let spacing = Styles.Sizes.columnSpacing / 2
|
||||
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -spacing, bottom: 0, right: spacing)
|
||||
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: -spacing)
|
||||
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: spacing)
|
||||
|
||||
contentView.addSubview(button)
|
||||
button.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
}
|
||||
|
||||
border = .tail
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
32
Classes/PullRequestReviews/PullRequestReviewReplyModel.swift
Normal file
32
Classes/PullRequestReviews/PullRequestReviewReplyModel.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// PullRequestReviewReplyModel.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 1/29/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IGListKit
|
||||
|
||||
final class PullRequestReviewReplyModel: ListDiffable {
|
||||
|
||||
let replyID: Int
|
||||
|
||||
init(replyID: Int) {
|
||||
self.replyID = replyID
|
||||
}
|
||||
|
||||
// MARK: ListDiffable
|
||||
|
||||
func diffIdentifier() -> NSObjectProtocol {
|
||||
return "reply-\(replyID)" as NSObjectProtocol
|
||||
}
|
||||
|
||||
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
|
||||
if self === object { return true }
|
||||
guard let object = object as? PullRequestReviewReplyModel else { return false }
|
||||
return replyID == object.replyID
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// PullRequestReviewReplySectionController.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 1/29/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IGListKit
|
||||
|
||||
protocol PullRequestReviewReplySectionControllerDelegate: class {
|
||||
func didSelect(replySectionController: PullRequestReviewReplySectionController, reply: PullRequestReviewReplyModel)
|
||||
}
|
||||
|
||||
final class PullRequestReviewReplySectionController: ListGenericSectionController<PullRequestReviewReplyModel> {
|
||||
|
||||
private weak var delegate: PullRequestReviewReplySectionControllerDelegate?
|
||||
|
||||
init(delegate: PullRequestReviewReplySectionControllerDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
override func sizeForItem(at index: Int) -> CGSize {
|
||||
guard let width = collectionContext?.insetContainerSize.width else { return .zero }
|
||||
return CGSize(width: width, height: Styles.Sizes.tableCellHeight)
|
||||
}
|
||||
|
||||
override func cellForItem(at index: Int) -> UICollectionViewCell {
|
||||
guard let cell = collectionContext?.dequeueReusableCell(of: PullRequestReviewReplyCell.self, for: self, at: index) as? PullRequestReviewReplyCell
|
||||
else { fatalError("Missing context or wrong cell") }
|
||||
return cell
|
||||
}
|
||||
|
||||
override func didSelectItem(at index: Int) {
|
||||
guard let object = self.object else { return }
|
||||
delegate?.didSelect(replySectionController: self, reply: object)
|
||||
}
|
||||
|
||||
}
|
||||
37
Classes/Systems/ListAdapter+Scrolling.swift
Normal file
37
Classes/Systems/ListAdapter+Scrolling.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// ListAdapter+Scrolling.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 2/4/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import IGListKit
|
||||
|
||||
extension ListAdapter {
|
||||
|
||||
func scroll(to object: Any, padding: CGFloat) {
|
||||
guard let collectionView = self.collectionView,
|
||||
let sectionController = sectionController(for: object)
|
||||
else { return }
|
||||
|
||||
let itemIndex = sectionController.numberOfItems() - 1
|
||||
let path = IndexPath(item: itemIndex, section: sectionController.section)
|
||||
|
||||
guard let attributes = collectionView.layoutAttributesForItem(at: path) else { return }
|
||||
|
||||
let paddedMaxY = min(attributes.frame.maxY + padding, collectionView.contentSize.height)
|
||||
let viewportHeight = collectionView.bounds.height
|
||||
|
||||
// make sure not already at the top
|
||||
guard paddedMaxY > viewportHeight else { return }
|
||||
|
||||
let offset = paddedMaxY - viewportHeight
|
||||
collectionView.setContentOffset(
|
||||
CGPoint(x: collectionView.contentOffset.x, y: offset),
|
||||
animated: trueUnlessReduceMotionEnabled
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
32
Classes/Views/MessageView+Styles.swift
Normal file
32
Classes/Views/MessageView+Styles.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// MessageView+Styles.swift
|
||||
// Freetime
|
||||
//
|
||||
// Created by Ryan Nystrom on 2/4/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MessageViewController
|
||||
|
||||
extension MessageViewController {
|
||||
|
||||
func configure(target: Any, action: Selector) {
|
||||
// setup message view properties
|
||||
borderColor = Styles.Colors.Gray.border.color
|
||||
messageView.textView.placeholderText = NSLocalizedString("Leave a comment", comment: "")
|
||||
messageView.textView.placeholderTextColor = Styles.Colors.Gray.light.color
|
||||
messageView.keyboardType = .twitter
|
||||
messageView.set(buttonIcon: UIImage(named: "send")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
messageView.buttonTint = Styles.Colors.Blue.medium.color
|
||||
messageView.font = Styles.Fonts.body
|
||||
messageView.inset = UIEdgeInsets(
|
||||
top: Styles.Sizes.gutter,
|
||||
left: Styles.Sizes.gutter,
|
||||
bottom: Styles.Sizes.rowSpacing / 2,
|
||||
right: Styles.Sizes.gutter
|
||||
)
|
||||
messageView.addButton(target: target, action: action)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -208,6 +208,8 @@
|
||||
297A372C1F1700BC0081C04E /* IssueRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297A372B1F1700BC0081C04E /* IssueRequestModel.swift */; };
|
||||
297A372E1F17018F0081C04E /* IssueRequestCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297A372D1F17018F0081C04E /* IssueRequestCell.swift */; };
|
||||
297A37301F1704C10081C04E /* IssueRequestSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297A372F1F1704C10081C04E /* IssueRequestSectionController.swift */; };
|
||||
297A6CE4202774830027E03B /* ListAdapter+Scrolling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297A6CE3202774830027E03B /* ListAdapter+Scrolling.swift */; };
|
||||
297A6CE62027880C0027E03B /* MessageView+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297A6CE52027880C0027E03B /* MessageView+Styles.swift */; };
|
||||
297AE8791EC0D5C200B44A1F /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297AE85F1EC0D5C100B44A1F /* App.swift */; };
|
||||
297AE87A1EC0D5C200B44A1F /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297AE8601EC0D5C100B44A1F /* Authorization.swift */; };
|
||||
297AE87E1EC0D5C200B44A1F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297AE8671EC0D5C200B44A1F /* AppDelegate.swift */; };
|
||||
@@ -326,6 +328,9 @@
|
||||
29DA1E881F5E2B8A0050C64B /* ClearAllHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DA1E871F5E2B8A0050C64B /* ClearAllHeaderCell.swift */; };
|
||||
29DA1E8A1F5E2DEC0050C64B /* SearchRecentHeaderSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DA1E891F5E2DEC0050C64B /* SearchRecentHeaderSectionController.swift */; };
|
||||
29DA1E8C1F5F8CC40050C64B /* MarkdownAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DA1E8B1F5F8CC40050C64B /* MarkdownAttribute.swift */; };
|
||||
29DAA7AB20202A1A0029277A /* PullRequestReviewReplySectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DAA7AA20202A1A0029277A /* PullRequestReviewReplySectionController.swift */; };
|
||||
29DAA7AD20202A320029277A /* PullRequestReviewReplyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DAA7AC20202A320029277A /* PullRequestReviewReplyCell.swift */; };
|
||||
29DAA7AF20202BEA0029277A /* PullRequestReviewReplyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DAA7AE20202BEA0029277A /* PullRequestReviewReplyModel.swift */; };
|
||||
29DB264A1FCA10A800C3D0C9 /* GithubHighlighting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DB26491FCA10A800C3D0C9 /* GithubHighlighting.swift */; };
|
||||
29EB1EEF1F425E5100A200B4 /* ForegroundHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EB1EEE1F425E5100A200B4 /* ForegroundHandler.swift */; };
|
||||
29EDFE7C1F65C580005BCCEB /* SplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EDFE7B1F65C580005BCCEB /* SplitViewTests.swift */; };
|
||||
@@ -628,6 +633,8 @@
|
||||
297A372B1F1700BC0081C04E /* IssueRequestModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueRequestModel.swift; sourceTree = "<group>"; };
|
||||
297A372D1F17018F0081C04E /* IssueRequestCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueRequestCell.swift; sourceTree = "<group>"; };
|
||||
297A372F1F1704C10081C04E /* IssueRequestSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueRequestSectionController.swift; sourceTree = "<group>"; };
|
||||
297A6CE3202774830027E03B /* ListAdapter+Scrolling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListAdapter+Scrolling.swift"; sourceTree = "<group>"; };
|
||||
297A6CE52027880C0027E03B /* MessageView+Styles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageView+Styles.swift"; sourceTree = "<group>"; };
|
||||
297AE8341EC0D58A00B44A1F /* Freetime.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Freetime.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
297AE8481EC0D58A00B44A1F /* FreetimeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FreetimeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
297AE84C1EC0D58A00B44A1F /* DateDisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDisplayTests.swift; sourceTree = "<group>"; };
|
||||
@@ -755,6 +762,9 @@
|
||||
29DA1E871F5E2B8A0050C64B /* ClearAllHeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClearAllHeaderCell.swift; sourceTree = "<group>"; };
|
||||
29DA1E891F5E2DEC0050C64B /* SearchRecentHeaderSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchRecentHeaderSectionController.swift; sourceTree = "<group>"; };
|
||||
29DA1E8B1F5F8CC40050C64B /* MarkdownAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownAttribute.swift; sourceTree = "<group>"; };
|
||||
29DAA7AA20202A1A0029277A /* PullRequestReviewReplySectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestReviewReplySectionController.swift; sourceTree = "<group>"; };
|
||||
29DAA7AC20202A320029277A /* PullRequestReviewReplyCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestReviewReplyCell.swift; sourceTree = "<group>"; };
|
||||
29DAA7AE20202BEA0029277A /* PullRequestReviewReplyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestReviewReplyModel.swift; sourceTree = "<group>"; };
|
||||
29DB26491FCA10A800C3D0C9 /* GithubHighlighting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubHighlighting.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>"; };
|
||||
@@ -1418,6 +1428,7 @@
|
||||
29316DBC1ECC8970007CAE3F /* GithubUserSession.swift */,
|
||||
296B4E301F7C805600C16887 /* GraphQLIDDecode.swift */,
|
||||
294B11231F7B37D200E04F2D /* ImageCellHeightCache.swift */,
|
||||
297A6CE3202774830027E03B /* ListAdapter+Scrolling.swift */,
|
||||
298BA08C1EC90A9000B01946 /* NSAttributedStringSizing.swift */,
|
||||
292CD3D31F0DC12100D3D57B /* PhotoViewHandler.swift */,
|
||||
2980033A1F51E82400BE90F4 /* Rating */,
|
||||
@@ -1428,12 +1439,12 @@
|
||||
3E79A2FE1F8A7DA700E1126B /* ShortcutHandler.swift */,
|
||||
29973E551F68BFDE0004B693 /* Signature.swift */,
|
||||
2971722A1F069E6B005E43AC /* SpinnerSectionController.swift */,
|
||||
29CCB28A1FDDFFA200E23FA0 /* String+GithubDate.swift */,
|
||||
29416BFC1F118DD700D03E1A /* String+QueryItemValue.swift */,
|
||||
293971881F904C82002FAC4B /* Toast.swift */,
|
||||
2939718A1F904D2A002FAC4B /* Toast+GitHawk.swift */,
|
||||
292CD3CF1F0DBB5C00D3D57B /* WebviewCellHeightCache.swift */,
|
||||
2930F2721F8A27750082BA26 /* WidthCache.swift */,
|
||||
29CCB28A1FDDFFA200E23FA0 /* String+GithubDate.swift */,
|
||||
D8BAD0651FDF224600C41071 /* WrappingStaticSpacingFlowLayout.swift */,
|
||||
);
|
||||
path = Systems;
|
||||
@@ -1492,6 +1503,7 @@
|
||||
98835BD11F1A158D005BA24F /* LabelCell.swift */,
|
||||
D8BAD05F1FDA0A1A00C41071 /* LabelListCell.swift */,
|
||||
D8BAD0631FDF221900C41071 /* LabelListView.swift */,
|
||||
297A6CE52027880C0027E03B /* MessageView+Styles.swift */,
|
||||
29136BE0200A7D3D007317BE /* NavigationTitleDropdownView.swift */,
|
||||
2963A9331EE2118E0066509C /* ResponderButton.swift */,
|
||||
29C33FDE1F128D4400EC8D40 /* SelectableCell.swift */,
|
||||
@@ -1640,6 +1652,9 @@
|
||||
children = (
|
||||
29CC292E1FF421DC006B6DE7 /* GithubClient+PullRequestReviewComments.swift */,
|
||||
29CC292F1FF421DC006B6DE7 /* PullRequestReviewCommentsViewController.swift */,
|
||||
29DAA7AC20202A320029277A /* PullRequestReviewReplyCell.swift */,
|
||||
29DAA7AE20202BEA0029277A /* PullRequestReviewReplyModel.swift */,
|
||||
29DAA7AA20202A1A0029277A /* PullRequestReviewReplySectionController.swift */,
|
||||
);
|
||||
path = PullRequestReviews;
|
||||
sourceTree = "<group>";
|
||||
@@ -2246,6 +2261,7 @@
|
||||
files = (
|
||||
D8BAD0601FDA0A1A00C41071 /* LabelListCell.swift in Sources */,
|
||||
290744A91F24D2DA00FD9E48 /* AddCommentClient.swift in Sources */,
|
||||
29DAA7AD20202A320029277A /* PullRequestReviewReplyCell.swift in Sources */,
|
||||
29316DC51ECC9841007CAE3F /* Alamofire+GithubAPI.swift in Sources */,
|
||||
75A0ACF51F79A82D0062D99A /* AlertAction.swift in Sources */,
|
||||
75468F7A1F7AFBC800F2BC19 /* AlertActionBuilder.swift in Sources */,
|
||||
@@ -2402,6 +2418,7 @@
|
||||
2928C78A1F15D7E00000D06D /* IssueRenamedCell.swift in Sources */,
|
||||
DC63393B1F9F65EE00402A8D /* RepositoryAttributedString.swift in Sources */,
|
||||
2928C7881F15D7C50000D06D /* IssueRenamedModel.swift in Sources */,
|
||||
297A6CE62027880C0027E03B /* MessageView+Styles.swift in Sources */,
|
||||
2928C78C1F15D80E0000D06D /* IssueRenamedSectionController.swift in Sources */,
|
||||
2928C78E1F15DF1B0000D06D /* IssueRenamedString.swift in Sources */,
|
||||
297A372E1F17018F0081C04E /* IssueRequestCell.swift in Sources */,
|
||||
@@ -2490,6 +2507,7 @@
|
||||
2980033E1F51E93500BE90F4 /* RatingSectionController.swift in Sources */,
|
||||
29FF85A51EE1EA7A007B8762 /* ReactionContent+ReactionType.swift in Sources */,
|
||||
292FCB1D1EDFCD3D0026635E /* ReactionViewModel.swift in Sources */,
|
||||
29DAA7AF20202BEA0029277A /* PullRequestReviewReplyModel.swift in Sources */,
|
||||
29A5AF3F1F9266370065D529 /* NotificationViewModel+Filterable.swift in Sources */,
|
||||
29B94E691FCB36A000715D7E /* File+ListDiffable.swift in Sources */,
|
||||
29C9FDD31EC65FEE00EE3A52 /* Repository.swift in Sources */,
|
||||
@@ -2506,6 +2524,7 @@
|
||||
986B87341F2CAE9800AAB55C /* RepositoryIssueSummaryType.swift in Sources */,
|
||||
2905AFAD1F7357C50015AE32 /* RepositoryIssuesViewController.swift in Sources */,
|
||||
29EE1C1D1F3A33890046A54D /* RepositoryLabel.swift in Sources */,
|
||||
29DAA7AB20202A1A0029277A /* PullRequestReviewReplySectionController.swift in Sources */,
|
||||
29792B1D1FFB2FC6007A0C57 /* IssueManagingNavSectionController.swift in Sources */,
|
||||
2905AFAB1F7357B40015AE32 /* RepositoryOverviewViewController.swift in Sources */,
|
||||
29EDFE821F661562005BCCEB /* RepositoryReadmeModel.swift in Sources */,
|
||||
@@ -2526,6 +2545,7 @@
|
||||
29CC29311FF421DC006B6DE7 /* PullRequestReviewCommentsViewController.swift in Sources */,
|
||||
986B872B1F2C842000AAB55C /* SearchNoResultsSectionController.swift in Sources */,
|
||||
29DA1E841F5E26310050C64B /* SearchRecentCell.swift in Sources */,
|
||||
297A6CE4202774830027E03B /* ListAdapter+Scrolling.swift in Sources */,
|
||||
DC7857101F97F546009BADDA /* Debouncer.swift in Sources */,
|
||||
98F9F4031F9CD006005A0266 /* Image+Base64.swift in Sources */,
|
||||
29DA1E881F5E2B8A0050C64B /* ClearAllHeaderCell.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user