Finish wiring up edit comments (#594)

This commit is contained in:
Ryan Nystrom
2017-10-18 12:14:17 -04:00
committed by GitHub
parent 55a0064495
commit 357e7e1bde
14 changed files with 211 additions and 65 deletions

View File

@@ -20,6 +20,7 @@ final class IssueCommentModel: ListDiffable {
let rawMarkdown: String
let viewerCanUpdate: Bool
let viewerCanDelete: Bool
let isRoot: Bool
enum ThreadState {
case single
@@ -37,7 +38,8 @@ final class IssueCommentModel: ListDiffable {
threadState: ThreadState,
rawMarkdown: String,
viewerCanUpdate: Bool,
viewerCanDelete: Bool
viewerCanDelete: Bool,
isRoot: Bool
) {
self.id = id
self.details = details
@@ -49,6 +51,7 @@ final class IssueCommentModel: ListDiffable {
self.rawMarkdown = rawMarkdown
self.viewerCanUpdate = viewerCanUpdate
self.viewerCanDelete = viewerCanDelete
self.isRoot = isRoot
}
// MARK: ListDiffable

View File

@@ -19,7 +19,8 @@ final class IssueCommentSectionController: ListBindingSectionController<IssueCom
ListBindingSectionControllerSelectionDelegate,
IssueCommentDetailCellDelegate,
IssueCommentReactionCellDelegate,
AttributedStringViewIssueDelegate {
AttributedStringViewIssueDelegate,
EditCommentViewControllerDelegate {
private var collapsed = true
private let generator = UIImpactFeedbackGenerator()
@@ -41,6 +42,9 @@ AttributedStringViewIssueDelegate {
// set when sending a mutation and override the original issue query reactions
private var reactionMutation: IssueCommentReactionViewModel? = nil
// set after succesfully editing the body
private var bodyEdits: (markdown: String, models: [ListDiffable])? = nil
init(model: IssueDetailsModel, client: GithubClient, delegate: IssueCommentSectionControllerDelegate) {
self.model = model
self.client = client
@@ -96,29 +100,43 @@ AttributedStringViewIssueDelegate {
}
}
func edit(sender: UIView) -> UIAlertAction? {
func editAction() -> UIAlertAction? {
guard object?.viewerCanUpdate == true else { return nil }
return UIAlertAction(title: NSLocalizedString("Edit", comment: ""), style: .default, handler: { [weak self] _ in
guard let markdown = self?.object?.rawMarkdown,
let owner = self?.model.owner,
let repo = self?.model.repo
return UIAlertAction(title: NSLocalizedString("Edit Comment", comment: ""), style: .default, handler: { [weak self] _ in
guard let markdown = self?.bodyEdits?.markdown ?? self?.object?.rawMarkdown,
let issueModel = self?.model,
let client = self?.client,
let commentID = self?.object?.number,
let isRoot = self?.object?.isRoot
else { return }
let edit = EditCommentViewController(markdown: markdown, owner: owner, repo: repo)
let edit = EditCommentViewController(
client: client,
markdown: markdown,
issueModel: issueModel,
commentID: commentID,
isRoot: isRoot
)
edit.delegate = self
let nav = UINavigationController(rootViewController: edit)
nav.modalPresentationStyle = .formSheet
self?.viewController?.present(nav, animated: true, completion: nil)
})
}
@discardableResult
private func uncollapse() -> Bool {
guard collapsed else { return false }
collapsed = false
private func clearCollapseCells() {
// clear any collapse state before updating so we don't have a dangling overlay
for cell in collectionContext?.visibleCells(for: self) ?? [] {
if let cell = cell as? CollapsibleCell {
cell.setCollapse(visible: false)
}
}
}
@discardableResult
private func uncollapse() -> Bool {
guard collapsed else { return false }
collapsed = false
clearCollapseCells()
update(animated: true)
return true
}
@@ -176,7 +194,8 @@ AttributedStringViewIssueDelegate {
guard !hasBeenDeleted else { return [] }
var bodies = [ListDiffable]()
for body in object.bodyModels {
let bodyModels = bodyEdits?.models ?? object.bodyModels
for body in bodyModels {
bodies.append(body)
if collapsed && body === object.collapse?.model {
break
@@ -282,6 +301,7 @@ AttributedStringViewIssueDelegate {
alert.popoverPresentationController?.sourceView = sender
alert.addActions([
shareAction(sender: sender),
editAction(),
deleteAction(),
AlertAction.cancel()
])
@@ -320,4 +340,22 @@ AttributedStringViewIssueDelegate {
viewController?.show(controller, sender: nil)
}
// MARK: EditCommentViewControllerDelegate
func didEditComment(viewController: EditCommentViewController, markdown: String) {
viewController.dismiss(animated: true)
guard let width = collectionContext?.containerSize.width else { return }
let options = commentModelOptions(owner: model.owner, repo: model.repo)
let bodyModels = CreateCommentModels(markdown: markdown, width: width, options: options)
bodyEdits = (markdown, bodyModels)
collapsed = false
clearCollapseCells()
update(animated: true)
}
func didCancel(viewController: EditCommentViewController) {
viewController.dismiss(animated: true)
}
}

View File

@@ -10,21 +10,34 @@ import UIKit
import SnapKit
protocol EditCommentViewControllerDelegate: class {
func didEditComment(viewController: EditCommentViewController, markdown: String)
func didCancel(viewController: EditCommentViewController)
}
class EditCommentViewController: UIViewController {
weak var delegate: EditCommentViewControllerDelegate? = nil
private let commentID: Int
private let client: GithubClient
private let textView = UITextView()
private let textActionsController = TextActionsController()
private let repo: String
private let owner: String
private let issueModel: IssueDetailsModel
private let isRoot: Bool
private let originalMarkdown: String
init(markdown: String, owner: String, repo: String) {
self.owner = owner
self.repo = repo
init(
client: GithubClient,
markdown: String,
issueModel: IssueDetailsModel,
commentID: Int,
isRoot: Bool
) {
self.client = client
self.issueModel = issueModel
self.commentID = commentID
self.isRoot = isRoot
self.originalMarkdown = markdown
super.init(nibName: nil, bundle: nil)
textView.text = markdown
}
@@ -50,12 +63,7 @@ class EditCommentViewController: UIViewController {
target: self,
action: #selector(EditCommentViewController.onCancel)
)
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("Save", comment: ""),
style: .done,
target: self,
action: #selector(EditCommentViewController.onSave)
)
setRightBarItemIdle()
let nc = NotificationCenter.default
nc.addObserver(
@@ -79,6 +87,15 @@ class EditCommentViewController: UIViewController {
// MARK: Private API
func setRightBarItemIdle() {
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("Save", comment: ""),
style: .done,
target: self,
action: #selector(EditCommentViewController.onSave)
)
}
func setupInputView() {
let getMarkdownBlock = { [weak self] () -> (String) in
return self?.textView.text ?? ""
@@ -86,8 +103,8 @@ class EditCommentViewController: UIViewController {
let actions = IssueTextActionsView.forMarkdown(
viewController: self,
getMarkdownBlock: getMarkdownBlock,
repo: repo,
owner: owner,
repo: issueModel.repo,
owner: issueModel.repo,
addBorder: true
)
textActionsController.configure(textView: textView, actions: actions)
@@ -96,29 +113,78 @@ class EditCommentViewController: UIViewController {
@objc
func onCancel() {
cancelAction_onCancel(
texts: [textView.text],
title: NSLocalizedString("Unsaved Changes", comment: ""),
message: NSLocalizedString("Are you sure you want to discard your edit? Your changes will be lost.",
comment: "")
)
textView.resignFirstResponder()
let dismissBlock = { [weak self] in
guard let strongSelf = self else { return }
strongSelf.delegate?.didCancel(viewController: strongSelf)
}
// if text unchanged, just dismiss
if originalMarkdown == textView.text {
dismissBlock()
} else {
let alert = UIAlertController(
title: NSLocalizedString("Unsaved Changes", comment: ""),
message: NSLocalizedString("Are you sure you want to discard your edit? Your changes will be lost.", comment: ""),
preferredStyle: .alert
)
alert.addActions([
AlertAction.goBack(),
AlertAction.discard { _ in
dismissBlock()
}
])
present(alert, animated: true, completion: nil)
}
}
@objc
func onSave() {
print("save")
setRightBarItemSpinning()
textView.isEditable = false
textView.resignFirstResponder()
let markdown = textView.text ?? ""
client.editComment(
owner: issueModel.owner,
repo: issueModel.repo,
issueNumber: issueModel.number,
commentID: commentID,
body: markdown,
isRoot: isRoot
) { [weak self] (result) in
switch result {
case .success: self?.didSave(markdown: markdown)
case .error: self?.error()
}
}
}
func didSave(markdown: String) {
navigationItem.rightBarButtonItem = nil
delegate?.didEditComment(viewController: self, markdown: markdown)
}
func error() {
setRightBarItemIdle()
ToastManager.showGenericError()
}
// MARK: Notifications
@objc
func onKeyboardWillShow(notification: NSNotification) {
print("kb will show")
guard let frame = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect else { return }
let inset = UIEdgeInsets(top: 0, left: 0, bottom: frame.height, right: 0)
textView.contentInset = inset
textView.scrollIndicatorInsets = inset
}
@objc
func onKeyboardWillHide(notification: NSNotification) {
print("kb will show")
textView.contentInset = .zero
textView.scrollIndicatorInsets = .zero
}
}

View File

@@ -0,0 +1,40 @@
//
// GithubClient+EditComment.swift
// Freetime
//
// Created by Ryan Nystrom on 10/16/17.
// Copyright © 2017 Ryan Nystrom. All rights reserved.
//
import Foundation
extension GithubClient {
func editComment(
owner: String,
repo: String,
issueNumber: Int,
commentID: Int,
body: String,
isRoot: Bool,
completion: @escaping (Result<Bool>) -> ()
) {
let path = isRoot
// https://developer.github.com/v3/issues/#edit-an-issue
? "repos/\(owner)/\(repo)/issues/\(issueNumber)"
// https://developer.github.com/v3/issues/comments/#edit-a-comment
: "repos/\(owner)/\(repo)/issues/comments/\(commentID)"
request(Request(
path: path,
method: .patch,
parameters: ["body": body],
completion: { (response, _) in
if response.response?.statusCode == 200 {
completion(.success(true))
} else {
completion(.error(response.error))
}
}))
}
}

View File

@@ -70,7 +70,8 @@ extension GithubClient {
repo: repo,
threadState: .single,
viewerCanUpdate: issueType.viewerCanUpdate,
viewerCanDelete: false // Root comment can not be deleted
viewerCanDelete: false, // Root comment can not be deleted
isRoot: true
)
let timeline = issueType.timelineViewModels(owner: owner, repo: repo, width: width)

View File

@@ -73,7 +73,8 @@ extension IssueOrPullRequestQuery.Data.Repository.IssueOrPullRequest.AsIssue: Is
repo: repo,
threadState: .single,
viewerCanUpdate: comment.fragments.updatableFields.viewerCanUpdate,
viewerCanDelete: comment.fragments.deletableFields.viewerCanDelete
viewerCanDelete: comment.fragments.deletableFields.viewerCanDelete,
isRoot: false
) {
results.append(model)

View File

@@ -45,6 +45,10 @@ func createIssueReactions(reactions: ReactionFields) -> IssueCommentReactionView
return IssueCommentReactionViewModel(models: models)
}
func commentModelOptions(owner: String, repo: String) -> GitHubMarkdownOptions {
return GitHubMarkdownOptions(owner: owner, repo: repo, flavors: [.issueShorthand, .usernames])
}
func createCommentModel(
id: String,
commentFields: CommentFields,
@@ -54,7 +58,8 @@ func createCommentModel(
repo: String,
threadState: IssueCommentModel.ThreadState,
viewerCanUpdate: Bool,
viewerCanDelete: Bool
viewerCanDelete: Bool,
isRoot: Bool
) -> IssueCommentModel? {
guard let author = commentFields.author,
let date = GithubAPIDateFormatter().date(from: commentFields.createdAt),
@@ -75,7 +80,7 @@ func createCommentModel(
editedAt: editedAt
)
let options = GitHubMarkdownOptions(owner: owner, repo: repo, flavors: [.issueShorthand, .usernames])
let options = commentModelOptions(owner: owner, repo: repo)
let bodies = CreateCommentModels(markdown: commentFields.body, width: width, options: options)
let reactions = createIssueReactions(reactions: reactionFields)
let collapse = IssueCollapsedBodies(bodies: bodies, width: width)
@@ -88,7 +93,8 @@ func createCommentModel(
threadState: threadState,
rawMarkdown: commentFields.body,
viewerCanUpdate: viewerCanUpdate,
viewerCanDelete: viewerCanDelete
viewerCanDelete: viewerCanDelete,
isRoot: isRoot
)
}

View File

@@ -511,7 +511,8 @@ IssueCommentSectionControllerDelegate {
repo: model.repo,
threadState: .single,
viewerCanUpdate: viewerCanUpdate,
viewerCanDelete: viewerCanDelete
viewerCanDelete: viewerCanDelete,
isRoot: false
)
else { return }
sentComments.append(comment)

View File

@@ -77,7 +77,8 @@ extension IssueOrPullRequestQuery.Data.Repository.IssueOrPullRequest.AsPullReque
repo: repo,
threadState: .single,
viewerCanUpdate: comment.fragments.updatableFields.viewerCanUpdate,
viewerCanDelete: comment.fragments.deletableFields.viewerCanDelete
viewerCanDelete: comment.fragments.deletableFields.viewerCanDelete,
isRoot: false
) {
results.append(model)
@@ -355,7 +356,8 @@ extension IssueOrPullRequestQuery.Data.Repository.IssueOrPullRequest.AsPullReque
repo: repo,
threadState: isTail ? .tail : .neck,
viewerCanUpdate: false, // unsupported by github
viewerCanDelete: false
viewerCanDelete: false,
isRoot: false
) {
results.append(model)
}

View File

@@ -110,12 +110,6 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg
// MARK: Private API
func setRightBarItemSpinning() {
let activity = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activity.startAnimating()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: activity)
}
func setRightBarItemIdle() {
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("Submit", comment: ""),

View File

@@ -90,12 +90,6 @@ TabNavRootViewControllerType {
BadgeNotifications.update(count: unreadCount)
}
func setRightBarItemSpinning() {
let activity = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activity.startAnimating()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: activity)
}
private func markAllRead() {
self.setRightBarItemSpinning()
self.client.markAllNotifications { success in

View File

@@ -10,14 +10,10 @@ import UIKit
extension UIViewController {
func showLoadingIndicator(_ show: Bool) {
if show {
let view = UIActivityIndicatorView(activityIndicatorStyle: .gray)
view.startAnimating()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: view)
} else {
navigationItem.rightBarButtonItem = nil
}
func setRightBarItemSpinning() {
let activity = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activity.startAnimating()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: activity)
}
}

View File

@@ -234,6 +234,7 @@
2980E0921F073E8B000E02C6 /* NotificationSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2980E0911F073E8B000E02C6 /* NotificationSectionController.swift */; };
2981A8A41EFE9FC700E25EF1 /* GithubEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2981A8A31EFE9FC700E25EF1 /* GithubEmoji.swift */; };
2982ED7A1F94EA8F00DBF8EB /* UICollectionViewCell+SafeAreaContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2982ED791F94EA8F00DBF8EB /* UICollectionViewCell+SafeAreaContentView.swift */; };
2982ED8B1F95900F00DBF8EB /* GithubClient+EditComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2982ED8A1F95900F00DBF8EB /* GithubClient+EditComment.swift */; };
298BA08D1EC90A9000B01946 /* NSAttributedStringSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BA08C1EC90A9000B01946 /* NSAttributedStringSizing.swift */; };
298BA08F1EC90FEE00B01946 /* UIView+BottomBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BA08E1EC90FEE00B01946 /* UIView+BottomBorder.swift */; };
298BA0971EC947F100B01946 /* SegmentedControlCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298BA0951EC947F100B01946 /* SegmentedControlCell.swift */; };
@@ -579,6 +580,7 @@
2981A8A31EFE9FC700E25EF1 /* GithubEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubEmoji.swift; sourceTree = "<group>"; };
2981A8A61EFEBEF900E25EF1 /* EmojiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiTests.swift; sourceTree = "<group>"; };
2982ED791F94EA8F00DBF8EB /* UICollectionViewCell+SafeAreaContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewCell+SafeAreaContentView.swift"; sourceTree = "<group>"; };
2982ED8A1F95900F00DBF8EB /* GithubClient+EditComment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GithubClient+EditComment.swift"; sourceTree = "<group>"; };
298744CA1F24B75300D5DF30 /* SlackTextViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SlackTextViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
298BA08C1EC90A9000B01946 /* NSAttributedStringSizing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSAttributedStringSizing.swift; sourceTree = "<group>"; };
298BA08E1EC90FEE00B01946 /* UIView+BottomBorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+BottomBorder.swift"; sourceTree = "<group>"; };
@@ -1371,6 +1373,7 @@
isa = PBXGroup;
children = (
29AF1E811F8AAB2B0008A0EF /* EditCommentViewController.swift */,
2982ED8A1F95900F00DBF8EB /* GithubClient+EditComment.swift */,
);
path = EditComment;
sourceTree = "<group>";
@@ -2148,6 +2151,7 @@
986B871D1F2B8FCD00AAB55C /* SearchViewController.swift in Sources */,
297AE87C1EC0D5C200B44A1F /* Secrets.swift in Sources */,
298BA0971EC947F100B01946 /* SegmentedControlCell.swift in Sources */,
2982ED8B1F95900F00DBF8EB /* GithubClient+EditComment.swift in Sources */,
297406981F0ED1E9003A6BFB /* SegmentedControlModel+Notifications.swift in Sources */,
298BA09A1EC947FC00B01946 /* SegmentedControlModel.swift in Sources */,
298BA0981EC947F100B01946 /* SegmentedControlSectionController.swift in Sources */,

View File

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