From 578ad4ea2cd9ea72ff37913061a9989867e40749 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Sun, 4 Nov 2018 13:48:06 -0500 Subject: [PATCH 01/34] Added Templates --- Classes/New Issue/IssueTemplates.swift | 220 ++++++++++++++++++ .../NewIssueTableViewController.swift | 22 ++ .../Repository/RepositoryViewController.swift | 23 +- Classes/Settings/SettingsViewController.swift | 24 +- Freetime.xcodeproj/project.pbxproj | 2 + 5 files changed, 264 insertions(+), 27 deletions(-) create mode 100644 Classes/New Issue/IssueTemplates.swift diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift new file mode 100644 index 000000000..653956093 --- /dev/null +++ b/Classes/New Issue/IssueTemplates.swift @@ -0,0 +1,220 @@ +// +// IssueTemplates.swift +// Freetime +// +// Created by Ehud Adler on 11/3/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import Foundation +import GitHubAPI +import GitHubSession +import Squawk + +struct IssueTemplate { + let title:String + let template:String +} + +enum IssueTemplateHelper { + + static private func matchesForRegexInText( + regex: String!, + text: String!) -> [String] { + + do { + let regex = try NSRegularExpression(pattern: regex, options: []) + let nsString = text as NSString + + let results = regex.matches( + in: text, + options: [], + range: NSMakeRange(0, nsString.length)) + return results.map { nsString.substring(with: $0.range)} + + } catch let error as NSError { + print("invalid regex: \(error.localizedDescription)") + return [] + } + } + + static func getNameAndDescriptionFromTemplateFile(file: String) -> (name: String?, about: String?) { + + let names = IssueTemplateHelper.matchesForRegexInText(regex: "(?<=name:).*", text: file) + let abouts = IssueTemplateHelper.matchesForRegexInText(regex: "(?<=about:).*", text: file) + let name = names.count > 0 + ? names[0].trimmingCharacters(in: .whitespaces) + : nil + let about = abouts.count > 0 + ? abouts[0].trimmingCharacters(in: .whitespaces) + : nil + return (name, about) + } + + static func showIssueAlert( + with templates: [IssueTemplate], + owner: String, + repo: String, + session: GitHubUserSession?, + mainViewController: UIViewController, + delegate: NewIssueTableViewControllerDelegate) { + + let alertView = UIAlertController( + title: NSLocalizedString("New Issue", comment: ""), + message: NSLocalizedString("Choose Template", comment: ""), + preferredStyle: .alert + ) + + for template in templates { + alertView.addAction( + UIAlertAction( + title: template.title, + style: .default, + handler: { _ in + + guard let viewController = NewIssueTableViewController.createWithTemplate( + client: GithubClient(userSession: session), + owner: owner, + repo: repo, + template: template.template, + signature: template.title == "Bug Report" ? .bugReport : .sentWithGitHawk + ) else { + Squawk.showGenericError() + return + } + viewController.delegate = delegate + let navController = UINavigationController(rootViewController: viewController) + navController.modalPresentationStyle = .formSheet + mainViewController.present(navController, animated: trueUnlessReduceMotionEnabled) + })) + } + + alertView.addAction( + UIAlertAction( + title: "Dismiss", + style: UIAlertActionStyle.cancel, + handler: { _ in + alertView.dismiss(animated: true, completion: nil) + }) + ) + mainViewController.present(alertView, animated: true, completion: nil) + } +} + +extension GithubClient { + + private func fetchTemplateFile( + owner: String, + repo: String, + filename: String, + completion: @escaping (Result) -> Void + ) { + + self.fetchFile( + owner: owner, + repo: repo, + branch: "master", + path: ".github/ISSUE_TEMPLATE/\(filename)") { (result) in + switch result { + case .success(let file): + completion(.success(file)) + case .error(let error): + completion(.error(error)) + case .nonUTF8: + completion(.error(nil)) + } + } + } + + private func createTemplateWith( + owner: String, + repo: String, + filename: String, + completion: @escaping (Result) -> Void + ) { + fetchTemplateFile(owner: owner, repo: repo, filename: filename) { (result) in + switch result { + case .success(let file): + let nameAndDescription = IssueTemplateHelper.getNameAndDescriptionFromTemplateFile(file: file) + if let name = nameAndDescription.name { + completion(.success(IssueTemplate(title: name, template: file))) + } else { + completion(.error(nil)) + } + case .error(let error): + completion(.error(error)) + } + } + } + + func createNewIssue( + owner: String, + repo: String, + session: GitHubUserSession?, + mainViewController: UIViewController, + delegate: NewIssueTableViewControllerDelegate) { + + var templates: [IssueTemplate] = [] + let templateGroup = DispatchGroup() + + self.fetchFiles( + owner: owner, + repo: repo, + branch: "master", + path: ".github/ISSUE_TEMPLATE") { (result) in + switch result { + case .success(let files): + for file in files { + templateGroup.enter() + self.createTemplateWith(owner: owner, repo: repo, filename: file.name, completion: { + switch $0 { + case .success(let template): + templates.append(template) + case .error(let error): + if let err = error { + Squawk.show(error: err) + } + } + templateGroup.leave() + }) + } + case .error(let error): + Squawk.show(error: error) + } + + // Wait for async calls in for-loop to finish up + templateGroup.notify(queue: .main) { + + // Sort lexicographically + let sortedTemplates = templates.sorted(by: {$0.title < $1.title }) + + if sortedTemplates.count > 0 { + // Templates exists... + IssueTemplateHelper.showIssueAlert( + with: sortedTemplates, + owner: owner, + repo: repo, + session: session, + mainViewController: mainViewController, + delegate: delegate + ) + } else { + // No templates exists, show blank new issue view controller + guard let viewController = NewIssueTableViewController.create( + client: GithubClient(userSession: session), + owner: owner, + repo: repo, + signature: .sentWithGitHawk + ) else { + Squawk.showGenericError() + return + } + viewController.delegate = delegate + let navController = UINavigationController(rootViewController: viewController) + navController.modalPresentationStyle = .formSheet + mainViewController.present(navController, animated: trueUnlessReduceMotionEnabled) + } + } + } + } +} diff --git a/Classes/New Issue/NewIssueTableViewController.swift b/Classes/New Issue/NewIssueTableViewController.swift index 343f903e3..27ba62552 100644 --- a/Classes/New Issue/NewIssueTableViewController.swift +++ b/Classes/New Issue/NewIssueTableViewController.swift @@ -51,6 +51,7 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg @IBOutlet var titleField: UITextField! @IBOutlet var bodyField: UITextView! + private var template: String = "" private var client: GithubClient! private var owner: String! private var repo: String! @@ -85,6 +86,26 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg return viewController } + static func createWithTemplate( + client: GithubClient, + owner: String, + repo: String, + template: String, + signature: IssueSignatureType? = nil) -> NewIssueTableViewController? { + + let storyboard = UIStoryboard(name: "NewIssue", bundle: nil) + + let viewController = storyboard.instantiateInitialViewController() as? NewIssueTableViewController + viewController?.hidesBottomBarWhenPushed = true + viewController?.client = client + viewController?.owner = owner + viewController?.repo = repo + viewController?.template = template + viewController?.signature = signature + + return viewController + } + override func viewDidLoad() { super.viewDidLoad() @@ -106,6 +127,7 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg // Setup markdown input view bodyField.githawkConfigure(inset: false) setupInputView() + bodyField.text = template // Update title to use localization title = Constants.Strings.newIssue diff --git a/Classes/Repository/RepositoryViewController.swift b/Classes/Repository/RepositoryViewController.swift index 4f82d81ff..5db768fb6 100644 --- a/Classes/Repository/RepositoryViewController.swift +++ b/Classes/Repository/RepositoryViewController.swift @@ -142,21 +142,16 @@ ContextMenuDelegate { } func newIssueAction() -> UIAlertAction? { - guard let newIssueViewController = NewIssueTableViewController.create( - client: client, - owner: repo.owner, - repo: repo.name, - signature: .sentWithGitHawk) - else { - Squawk.showGenericError() - return nil + let action = UIAlertAction(title: "New Issue", style: UIAlertActionStyle.default) { _ in + self.client.createNewIssue( + owner: self.repo.owner, + repo: self.repo.name, + session: nil, + mainViewController: self, + delegate: self + ) } - - newIssueViewController.delegate = self - weak var weakSelf = self - - return AlertAction(AlertActionBuilder { $0.rootViewController = weakSelf }) - .newIssue(issueController: newIssueViewController) + return action } func workingCopyAction() -> UIAlertAction? { diff --git a/Classes/Settings/SettingsViewController.swift b/Classes/Settings/SettingsViewController.swift index dcbb3a11a..5a7282ed1 100644 --- a/Classes/Settings/SettingsViewController.swift +++ b/Classes/Settings/SettingsViewController.swift @@ -162,19 +162,17 @@ NewIssueTableViewControllerDelegate, DefaultReactionDelegate { } func onReportBug() { - guard let viewController = NewIssueTableViewController.create( - client: GithubClient(userSession: sessionManager.focusedUserSession), - owner: "GitHawkApp", - repo: "GitHawk", - signature: .bugReport - ) else { - Squawk.showGenericError() - return - } - viewController.delegate = self - let navController = UINavigationController(rootViewController: viewController) - navController.modalPresentationStyle = .formSheet - present(navController, animated: trueUnlessReduceMotionEnabled) + showTemplateOptions() + } + + func showTemplateOptions() { + client.createNewIssue( + owner: "GitHawkApp", + repo: "GitHawk", + session: sessionManager.focusedUserSession, + mainViewController: self, + delegate: self + ) } func onViewSource() { diff --git a/Freetime.xcodeproj/project.pbxproj b/Freetime.xcodeproj/project.pbxproj index afc8cf4ec..9e381bffa 100644 --- a/Freetime.xcodeproj/project.pbxproj +++ b/Freetime.xcodeproj/project.pbxproj @@ -2161,6 +2161,7 @@ children = ( 98B5A0811F6C73D6000617D6 /* NewIssue.storyboard */, 98647DF21F758CCF00A4DE7A /* NewIssueTableViewController.swift */, + C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */, ); path = "New Issue"; sourceTree = ""; @@ -2948,6 +2949,7 @@ 29F3A18820CBFF8700645CB7 /* CreateNotificationTitle.swift in Sources */, 292EB08D1F1FF58D0046865D /* IssueMilestoneEventModel.swift in Sources */, 292EB0911F1FF72D0046865D /* IssueMilestoneEventSectionController.swift in Sources */, + C0DEB1DD218E7BFB00B6FEFD /* IssueTemplates.swift in Sources */, DCA5ED141FAEE8030072F074 /* Bookmark.swift in Sources */, 299F4A89204CEDDC004BA4F0 /* Client+AccessToken.swift in Sources */, 2931892F1F539C0E00EF0911 /* IssueMilestoneSectionController.swift in Sources */, From c46f3874cd2ec969b465c952879881639372627a Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Mon, 5 Nov 2018 16:53:49 -0500 Subject: [PATCH 02/34] Implemented review notes(1) --- Classes/New Issue/IssueTemplates.swift | 145 ++++++++++-------- .../NewIssueTableViewController.swift | 18 ++- .../Repository/RepositoryViewController.swift | 2 +- Freetime.xcodeproj/project.pbxproj | 2 + 4 files changed, 93 insertions(+), 74 deletions(-) diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift index 653956093..2fbe90034 100644 --- a/Classes/New Issue/IssueTemplates.swift +++ b/Classes/New Issue/IssueTemplates.swift @@ -12,25 +12,31 @@ import GitHubSession import Squawk struct IssueTemplate { - let title:String - let template:String + let title: String + let template: String +} + +struct IssueTemplateDetails { + let owner: String + let repo: String + let session: GitHubUserSession? + let viewController: UIViewController + weak var delegate: NewIssueTableViewControllerDelegate? } enum IssueTemplateHelper { - static private func matchesForRegexInText( - regex: String!, - text: String!) -> [String] { + static private func matches( + regex: String, + text: String) -> [String] { do { - let regex = try NSRegularExpression(pattern: regex, options: []) - let nsString = text as NSString - + let regex = try NSRegularExpression(pattern: regex) + let str = text as NSString let results = regex.matches( in: text, - options: [], - range: NSMakeRange(0, nsString.length)) - return results.map { nsString.substring(with: $0.range)} + range: NSMakeRange(0, str.length)) + return results.map { str.substring(with: $0.range)} } catch let error as NSError { print("invalid regex: \(error.localizedDescription)") @@ -38,26 +44,17 @@ enum IssueTemplateHelper { } } - static func getNameAndDescriptionFromTemplateFile(file: String) -> (name: String?, about: String?) { - - let names = IssueTemplateHelper.matchesForRegexInText(regex: "(?<=name:).*", text: file) - let abouts = IssueTemplateHelper.matchesForRegexInText(regex: "(?<=about:).*", text: file) - let name = names.count > 0 - ? names[0].trimmingCharacters(in: .whitespaces) - : nil - let about = abouts.count > 0 - ? abouts[0].trimmingCharacters(in: .whitespaces) - : nil + static func getNameAndDescription(fromTemplatefile file: String) -> (name: String?, about: String?) { + let names = IssueTemplateHelper.matches(regex: "(?<=name:).*", text: file) + let abouts = IssueTemplateHelper.matches(regex: "(?<=about:).*", text: file) + let name = names.first?.trimmingCharacters(in: .whitespaces) + let about = abouts.first?.trimmingCharacters(in: .whitespaces) return (name, about) } static func showIssueAlert( with templates: [IssueTemplate], - owner: String, - repo: String, - session: GitHubUserSession?, - mainViewController: UIViewController, - delegate: NewIssueTableViewControllerDelegate) { + details: IssueTemplateDetails) -> UIAlertController { let alertView = UIAlertController( title: NSLocalizedString("New Issue", comment: ""), @@ -73,31 +70,59 @@ enum IssueTemplateHelper { handler: { _ in guard let viewController = NewIssueTableViewController.createWithTemplate( - client: GithubClient(userSession: session), - owner: owner, - repo: repo, + client: GithubClient(userSession: details.session), + owner: details.owner, + repo: details.repo, template: template.template, signature: template.title == "Bug Report" ? .bugReport : .sentWithGitHawk ) else { - Squawk.showGenericError() + assertionFailure("Failed to create NewIssueTableViewController") return } - viewController.delegate = delegate + viewController.delegate = details.delegate let navController = UINavigationController(rootViewController: viewController) navController.modalPresentationStyle = .formSheet - mainViewController.present(navController, animated: trueUnlessReduceMotionEnabled) + details.viewController.present(navController, animated: trueUnlessReduceMotionEnabled) })) } alertView.addAction( UIAlertAction( - title: "Dismiss", - style: UIAlertActionStyle.cancel, + title: NSLocalizedString("Dismiss", comment: ""), + style: .cancel, handler: { _ in - alertView.dismiss(animated: true, completion: nil) + alertView.dismiss(animated: trueUnlessReduceMotionEnabled) }) ) - mainViewController.present(alertView, animated: true, completion: nil) + return alertView + } + + static func present( + withTemplates sortedTemplates: [IssueTemplate], + details: IssueTemplateDetails) { + if sortedTemplates.count > 0 { + // Templates exists... + let alertView = IssueTemplateHelper.showIssueAlert( + with: sortedTemplates, + details: details + ) + details.viewController.present(alertView, animated: trueUnlessReduceMotionEnabled) + } else { + // No templates exists, show blank new issue view controller + guard let viewController = NewIssueTableViewController.create( + client: GithubClient(userSession: details.session), + owner: details.owner, + repo: details.repo, + signature: .sentWithGitHawk + ) else { + assertionFailure("Failed to create NewIssueTableViewController") + return + } + viewController.delegate = details.delegate + let navController = UINavigationController(rootViewController: viewController) + navController.modalPresentationStyle = .formSheet + details.viewController.present(navController, animated: trueUnlessReduceMotionEnabled) + } } } @@ -126,7 +151,7 @@ extension GithubClient { } } - private func createTemplateWith( + private func createTemplate(withOwner owner: String, repo: String, filename: String, @@ -135,7 +160,7 @@ extension GithubClient { fetchTemplateFile(owner: owner, repo: repo, filename: filename) { (result) in switch result { case .success(let file): - let nameAndDescription = IssueTemplateHelper.getNameAndDescriptionFromTemplateFile(file: file) + let nameAndDescription = IssueTemplateHelper.getNameAndDescription(fromTemplatefile: file) if let name = nameAndDescription.name { completion(.success(IssueTemplate(title: name, template: file))) } else { @@ -155,8 +180,19 @@ extension GithubClient { delegate: NewIssueTableViewControllerDelegate) { var templates: [IssueTemplate] = [] + + // Create group. + // We need this since we will be making multiple async calls inside a for-loop. let templateGroup = DispatchGroup() + let details = IssueTemplateDetails( + owner: owner, + repo: repo, + session: session, + viewController: mainViewController, + delegate: delegate + ) + self.fetchFiles( owner: owner, repo: repo, @@ -166,7 +202,7 @@ extension GithubClient { case .success(let files): for file in files { templateGroup.enter() - self.createTemplateWith(owner: owner, repo: repo, filename: file.name, completion: { + self.createTemplate(withOwner: owner, repo: repo, filename: file.name, completion: { switch $0 { case .success(let template): templates.append(template) @@ -187,33 +223,10 @@ extension GithubClient { // Sort lexicographically let sortedTemplates = templates.sorted(by: {$0.title < $1.title }) - - if sortedTemplates.count > 0 { - // Templates exists... - IssueTemplateHelper.showIssueAlert( - with: sortedTemplates, - owner: owner, - repo: repo, - session: session, - mainViewController: mainViewController, - delegate: delegate - ) - } else { - // No templates exists, show blank new issue view controller - guard let viewController = NewIssueTableViewController.create( - client: GithubClient(userSession: session), - owner: owner, - repo: repo, - signature: .sentWithGitHawk - ) else { - Squawk.showGenericError() - return - } - viewController.delegate = delegate - let navController = UINavigationController(rootViewController: viewController) - navController.modalPresentationStyle = .formSheet - mainViewController.present(navController, animated: trueUnlessReduceMotionEnabled) - } + IssueTemplateHelper.present( + withTemplates: sortedTemplates, + details: details + ) } } } diff --git a/Classes/New Issue/NewIssueTableViewController.swift b/Classes/New Issue/NewIssueTableViewController.swift index 27ba62552..9459b33c3 100644 --- a/Classes/New Issue/NewIssueTableViewController.swift +++ b/Classes/New Issue/NewIssueTableViewController.swift @@ -70,11 +70,14 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg return raw } - static func create(client: GithubClient, - owner: String, - repo: String, - signature: IssueSignatureType? = nil) -> NewIssueTableViewController? { - let storyboard = UIStoryboard(name: "NewIssue", bundle: nil) + static func create( + client: GithubClient, + owner: String, + repo: String, + signature: IssueSignatureType? = nil + ) -> NewIssueTableViewController? { + + let storyboard = UIStoryboard(name: NSLocalizedString("NewIssue", comment: ""), bundle: nil) let viewController = storyboard.instantiateInitialViewController() as? NewIssueTableViewController viewController?.hidesBottomBarWhenPushed = true @@ -91,9 +94,10 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg owner: String, repo: String, template: String, - signature: IssueSignatureType? = nil) -> NewIssueTableViewController? { + signature: IssueSignatureType? = nil + ) -> NewIssueTableViewController? { - let storyboard = UIStoryboard(name: "NewIssue", bundle: nil) + let storyboard = UIStoryboard(name: NSLocalizedString("NewIssue", comment: ""), bundle: nil) let viewController = storyboard.instantiateInitialViewController() as? NewIssueTableViewController viewController?.hidesBottomBarWhenPushed = true diff --git a/Classes/Repository/RepositoryViewController.swift b/Classes/Repository/RepositoryViewController.swift index 5db768fb6..a1a4c7322 100644 --- a/Classes/Repository/RepositoryViewController.swift +++ b/Classes/Repository/RepositoryViewController.swift @@ -142,7 +142,7 @@ ContextMenuDelegate { } func newIssueAction() -> UIAlertAction? { - let action = UIAlertAction(title: "New Issue", style: UIAlertActionStyle.default) { _ in + let action = UIAlertAction(title: "New Issue", style: .default) { _ in self.client.createNewIssue( owner: self.repo.owner, repo: self.repo.name, diff --git a/Freetime.xcodeproj/project.pbxproj b/Freetime.xcodeproj/project.pbxproj index 9e381bffa..be0b357ed 100644 --- a/Freetime.xcodeproj/project.pbxproj +++ b/Freetime.xcodeproj/project.pbxproj @@ -466,6 +466,7 @@ BDB6AA6A215FBC35009BB73C /* RepositoryBranchesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA63215FBC35009BB73C /* RepositoryBranchesCell.swift */; }; BDB6AA6B215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA64215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift */; }; BDB6AA762165B8EA009BB73C /* SwitchBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */; }; + C0DEB1DD218E7BFB00B6FEFD /* IssueTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.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 */; }; @@ -1025,6 +1026,7 @@ 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 = ""; }; BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchBranches.swift; sourceTree = ""; }; + C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueTemplates.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 = ""; }; From 76d4ec27a4e532c4f83d6684e85a2252b3d20822 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Mon, 5 Nov 2018 16:56:23 -0500 Subject: [PATCH 03/34] Implement review notes(2) --- Classes/New Issue/NewIssueTableViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/New Issue/NewIssueTableViewController.swift b/Classes/New Issue/NewIssueTableViewController.swift index 9459b33c3..952a3bd36 100644 --- a/Classes/New Issue/NewIssueTableViewController.swift +++ b/Classes/New Issue/NewIssueTableViewController.swift @@ -51,7 +51,7 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg @IBOutlet var titleField: UITextField! @IBOutlet var bodyField: UITextView! - private var template: String = "" + private var template = "" private var client: GithubClient! private var owner: String! private var repo: String! From ec7d437c00c84e3eb0a7a1a40335bac679cf60d1 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Fri, 9 Nov 2018 14:41:41 -0500 Subject: [PATCH 04/34] Switched from master to default branch for template lookup --- Classes/New Issue/IssueTemplates.swift | 56 +++++++++---------- .../Repository/RepositoryViewController.swift | 3 +- Classes/Settings/SettingsViewController.swift | 9 ++- Classes/Views/Constants.swift | 3 + 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift index 2fbe90034..58287f966 100644 --- a/Classes/New Issue/IssueTemplates.swift +++ b/Classes/New Issue/IssueTemplates.swift @@ -17,8 +17,7 @@ struct IssueTemplate { } struct IssueTemplateDetails { - let owner: String - let repo: String + let repo: RepositoryDetails let session: GitHubUserSession? let viewController: UIViewController weak var delegate: NewIssueTableViewControllerDelegate? @@ -26,7 +25,6 @@ struct IssueTemplateDetails { enum IssueTemplateHelper { - static private func matches( regex: String, text: String) -> [String] { @@ -71,8 +69,8 @@ enum IssueTemplateHelper { guard let viewController = NewIssueTableViewController.createWithTemplate( client: GithubClient(userSession: details.session), - owner: details.owner, - repo: details.repo, + owner: details.repo.owner, + repo: details.repo.name, template: template.template, signature: template.title == "Bug Report" ? .bugReport : .sentWithGitHawk ) else { @@ -111,8 +109,8 @@ enum IssueTemplateHelper { // No templates exists, show blank new issue view controller guard let viewController = NewIssueTableViewController.create( client: GithubClient(userSession: details.session), - owner: details.owner, - repo: details.repo, + owner: details.repo.owner, + repo: details.repo.name, signature: .sentWithGitHawk ) else { assertionFailure("Failed to create NewIssueTableViewController") @@ -129,35 +127,30 @@ enum IssueTemplateHelper { extension GithubClient { private func fetchTemplateFile( - owner: String, - repo: String, + repo: RepositoryDetails, filename: String, completion: @escaping (Result) -> Void ) { self.fetchFile( - owner: owner, - repo: repo, - branch: "master", - path: ".github/ISSUE_TEMPLATE/\(filename)") { (result) in + owner: repo.owner, + repo: repo.name, + branch: repo.defaultBranch, + path: "\(Constants.Filepaths.githubIssue)/\(filename)") { (result) in switch result { - case .success(let file): - completion(.success(file)) - case .error(let error): - completion(.error(error)) - case .nonUTF8: - completion(.error(nil)) + case .success(let file): completion(.success(file)) + case .error(let error): completion(.error(error)) + case .nonUTF8: completion(.error(nil)) } } } - private func createTemplate(withOwner - owner: String, - repo: String, + private func createTemplate( + repo: RepositoryDetails, filename: String, completion: @escaping (Result) -> Void ) { - fetchTemplateFile(owner: owner, repo: repo, filename: filename) { (result) in + fetchTemplateFile(repo: repo, filename: filename) { (result) in switch result { case .success(let file): let nameAndDescription = IssueTemplateHelper.getNameAndDescription(fromTemplatefile: file) @@ -173,8 +166,7 @@ extension GithubClient { } func createNewIssue( - owner: String, - repo: String, + repo: RepositoryDetails, session: GitHubUserSession?, mainViewController: UIViewController, delegate: NewIssueTableViewControllerDelegate) { @@ -186,7 +178,6 @@ extension GithubClient { let templateGroup = DispatchGroup() let details = IssueTemplateDetails( - owner: owner, repo: repo, session: session, viewController: mainViewController, @@ -194,15 +185,18 @@ extension GithubClient { ) self.fetchFiles( - owner: owner, - repo: repo, - branch: "master", - path: ".github/ISSUE_TEMPLATE") { (result) in + owner: repo.owner, + repo: repo.name, + branch: repo.defaultBranch, + path: Constants.Filepaths.githubIssue) { (result) in switch result { case .success(let files): for file in files { templateGroup.enter() - self.createTemplate(withOwner: owner, repo: repo, filename: file.name, completion: { + self.createTemplate( + repo: repo, + filename: file.name, + completion: { switch $0 { case .success(let template): templates.append(template) diff --git a/Classes/Repository/RepositoryViewController.swift b/Classes/Repository/RepositoryViewController.swift index a1a4c7322..e9ab3ae31 100644 --- a/Classes/Repository/RepositoryViewController.swift +++ b/Classes/Repository/RepositoryViewController.swift @@ -144,8 +144,7 @@ ContextMenuDelegate { func newIssueAction() -> UIAlertAction? { let action = UIAlertAction(title: "New Issue", style: .default) { _ in self.client.createNewIssue( - owner: self.repo.owner, - repo: self.repo.name, + repo: self.repo, session: nil, mainViewController: self, delegate: self diff --git a/Classes/Settings/SettingsViewController.swift b/Classes/Settings/SettingsViewController.swift index 5a7282ed1..9f3e13d72 100644 --- a/Classes/Settings/SettingsViewController.swift +++ b/Classes/Settings/SettingsViewController.swift @@ -166,9 +166,14 @@ NewIssueTableViewControllerDelegate, DefaultReactionDelegate { } func showTemplateOptions() { - client.createNewIssue( + let repo = RepositoryDetails( owner: "GitHawkApp", - repo: "GitHawk", + name: "GitHawk", + defaultBranch: "master", + hasIssuesEnabled: true + ) + client.createNewIssue( + repo: repo, session: sessionManager.focusedUserSession, mainViewController: self, delegate: self diff --git a/Classes/Views/Constants.swift b/Classes/Views/Constants.swift index 966792c8d..7d941ce59 100644 --- a/Classes/Views/Constants.swift +++ b/Classes/Views/Constants.swift @@ -10,6 +10,9 @@ import Foundation enum Constants { + enum Filepaths { + static let githubIssue = ".github/ISSUE_TEMPLATE" + } enum URLs { static let website = "http://www.githawk.com/" static let blog = "http://blog.githawk.com/" From a175329dd93d1edd6a9e504a5513f56ae45bb155 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Fri, 9 Nov 2018 15:27:28 -0500 Subject: [PATCH 05/34] Created SpinnerTabelCell to allow spinner on styledTextCell Show/Stop spinner when fetching templates --- Classes/New Issue/IssueTemplates.swift | 7 ++-- .../Repository/RepositoryViewController.swift | 6 ++-- Classes/Settings/Settings.storyboard | 6 ++-- Classes/Settings/SettingsViewController.swift | 15 ++++---- Classes/Views/SpinnerTableCell.swift | 35 +++++++++++++++++++ Freetime.xcodeproj/project.pbxproj | 4 +++ 6 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 Classes/Views/SpinnerTableCell.swift diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift index 58287f966..3dc9606b3 100644 --- a/Classes/New Issue/IssueTemplates.swift +++ b/Classes/New Issue/IssueTemplates.swift @@ -169,7 +169,9 @@ extension GithubClient { repo: RepositoryDetails, session: GitHubUserSession?, mainViewController: UIViewController, - delegate: NewIssueTableViewControllerDelegate) { + delegate: NewIssueTableViewControllerDelegate, + completion: @escaping () -> Void + ) { var templates: [IssueTemplate] = [] @@ -210,11 +212,12 @@ extension GithubClient { } case .error(let error): Squawk.show(error: error) + completion() } // Wait for async calls in for-loop to finish up templateGroup.notify(queue: .main) { - + completion() // Sort lexicographically let sortedTemplates = templates.sorted(by: {$0.title < $1.title }) IssueTemplateHelper.present( diff --git a/Classes/Repository/RepositoryViewController.swift b/Classes/Repository/RepositoryViewController.swift index e9ab3ae31..7339b9211 100644 --- a/Classes/Repository/RepositoryViewController.swift +++ b/Classes/Repository/RepositoryViewController.swift @@ -147,8 +147,10 @@ ContextMenuDelegate { repo: self.repo, session: nil, mainViewController: self, - delegate: self - ) + delegate: self, + completion: { + + }) } return action } diff --git a/Classes/Settings/Settings.storyboard b/Classes/Settings/Settings.storyboard index 8b592e44d..ad06493b7 100644 --- a/Classes/Settings/Settings.storyboard +++ b/Classes/Settings/Settings.storyboard @@ -1,11 +1,11 @@ - + - + @@ -125,7 +125,7 @@ - + diff --git a/Classes/Settings/SettingsViewController.swift b/Classes/Settings/SettingsViewController.swift index 9f3e13d72..a9e2c7ce0 100644 --- a/Classes/Settings/SettingsViewController.swift +++ b/Classes/Settings/SettingsViewController.swift @@ -23,7 +23,7 @@ NewIssueTableViewControllerDelegate, DefaultReactionDelegate { @IBOutlet weak var githubStatusCell: StyledTableCell! @IBOutlet weak var reviewOnAppStoreCell: StyledTableCell! @IBOutlet weak var tryTestFlightBetaCell: StyledTableCell! - @IBOutlet weak var reportBugCell: StyledTableCell! + @IBOutlet weak var reportBugCell: SpinnerTableCell! @IBOutlet weak var viewSourceCell: StyledTableCell! @IBOutlet weak var setDefaultReaction: StyledTableCell! @IBOutlet weak var signOutCell: StyledTableCell! @@ -172,12 +172,15 @@ NewIssueTableViewControllerDelegate, DefaultReactionDelegate { defaultBranch: "master", hasIssuesEnabled: true ) + + reportBugCell.showSpinner() client.createNewIssue( - repo: repo, - session: sessionManager.focusedUserSession, - mainViewController: self, - delegate: self - ) + repo: repo, + session: sessionManager.focusedUserSession, + mainViewController: self, + delegate: self) { + self.reportBugCell.stopSpinner() + } } func onViewSource() { diff --git a/Classes/Views/SpinnerTableCell.swift b/Classes/Views/SpinnerTableCell.swift new file mode 100644 index 000000000..a096df7f3 --- /dev/null +++ b/Classes/Views/SpinnerTableCell.swift @@ -0,0 +1,35 @@ +// +// SpinnerTableCell.swift +// Freetime +// +// Created by Ehud Adler on 11/9/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import UIKit + +final class SpinnerTableCell: StyledTableCell { + + let activity = UIActivityIndicatorView(activityIndicatorStyle: .gray) + var indicatorHolder: UITableViewCellAccessoryType? + + override func awakeFromNib() { + super.awakeFromNib() + contentView.addSubview(activity) + activity.snp.makeConstraints { make in + make.top.bottom.equalTo(self) + make.trailing.equalTo(self).inset(20) + } + activity.hidesWhenStopped = true + } + + public func showSpinner() { + indicatorHolder = self.accessoryType + self.accessoryType = .none + activity.startAnimating() + } + public func stopSpinner() { + activity.stopAnimating() + self.accessoryType = indicatorHolder ?? .none + } +} diff --git a/Freetime.xcodeproj/project.pbxproj b/Freetime.xcodeproj/project.pbxproj index be0b357ed..c18ada9f6 100644 --- a/Freetime.xcodeproj/project.pbxproj +++ b/Freetime.xcodeproj/project.pbxproj @@ -466,6 +466,7 @@ BDB6AA6A215FBC35009BB73C /* RepositoryBranchesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA63215FBC35009BB73C /* RepositoryBranchesCell.swift */; }; BDB6AA6B215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA64215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift */; }; BDB6AA762165B8EA009BB73C /* SwitchBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */; }; + C0BF305421961E1800A954D4 /* SpinnerTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BF305321961E1800A954D4 /* SpinnerTableCell.swift */; }; C0DEB1DD218E7BFB00B6FEFD /* IssueTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */; }; D8BAD0601FDA0A1A00C41071 /* LabelListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD05F1FDA0A1A00C41071 /* LabelListCell.swift */; }; D8BAD0641FDF221900C41071 /* LabelListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD0631FDF221900C41071 /* LabelListView.swift */; }; @@ -1026,6 +1027,7 @@ 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 = ""; }; BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchBranches.swift; sourceTree = ""; }; + C0BF305321961E1800A954D4 /* SpinnerTableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerTableCell.swift; sourceTree = ""; }; C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueTemplates.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 = ""; }; @@ -1807,6 +1809,7 @@ 29C2950F1EC7B7FF00D46CD2 /* ShowMoreDetailsLabel.swift */, 295840701EE9F4D3007723C6 /* ShowMoreDetailsLabel+Date.swift */, 2971722C1F069E96005E43AC /* SpinnerCell.swift */, + C0BF305321961E1800A954D4 /* SpinnerTableCell.swift */, 291929621F3FF0DA0012067B /* StyledTableCell.swift */, 299F63E7205F09900015D901 /* StyledTextRenderer+ListDiffable.swift */, 299F63D9205DD86E0015D901 /* StyledTextViewCell.swift */, @@ -3009,6 +3012,7 @@ 29F3A18C20CD790F00645CB7 /* UIViewController+CommonActionItems.swift in Sources */, 294563E61EE4EE6F00DBCD35 /* IssueStatusModel.swift in Sources */, 294563E81EE4EED200DBCD35 /* IssueStatusSectionController.swift in Sources */, + C0BF305421961E1800A954D4 /* SpinnerTableCell.swift in Sources */, 292FCB0F1EDFCC510026635E /* IssuesViewController.swift in Sources */, 29BBD82D20CACDFF004D62FE /* NotificationSectionController.swift in Sources */, 292FF8B01F2FDC33009E63F7 /* IssueTextActionsView.swift in Sources */, From 7568d572337a2b7743678d9dda0aa9a185593db5 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Fri, 9 Nov 2018 15:31:04 -0500 Subject: [PATCH 06/34] Condensed createWithTemplate into create "constructor" --- Classes/New Issue/IssueTemplates.swift | 2 +- .../NewIssueTableViewController.swift | 21 +------------------ 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift index 3dc9606b3..b30378e43 100644 --- a/Classes/New Issue/IssueTemplates.swift +++ b/Classes/New Issue/IssueTemplates.swift @@ -67,7 +67,7 @@ enum IssueTemplateHelper { style: .default, handler: { _ in - guard let viewController = NewIssueTableViewController.createWithTemplate( + guard let viewController = NewIssueTableViewController.create( client: GithubClient(userSession: details.session), owner: details.repo.owner, repo: details.repo.name, diff --git a/Classes/New Issue/NewIssueTableViewController.swift b/Classes/New Issue/NewIssueTableViewController.swift index 952a3bd36..a6c576bd2 100644 --- a/Classes/New Issue/NewIssueTableViewController.swift +++ b/Classes/New Issue/NewIssueTableViewController.swift @@ -74,31 +74,12 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg client: GithubClient, owner: String, repo: String, + template: String = "", signature: IssueSignatureType? = nil ) -> NewIssueTableViewController? { let storyboard = UIStoryboard(name: NSLocalizedString("NewIssue", comment: ""), bundle: nil) - let viewController = storyboard.instantiateInitialViewController() as? NewIssueTableViewController - viewController?.hidesBottomBarWhenPushed = true - viewController?.client = client - viewController?.owner = owner - viewController?.repo = repo - viewController?.signature = signature - - return viewController - } - - static func createWithTemplate( - client: GithubClient, - owner: String, - repo: String, - template: String, - signature: IssueSignatureType? = nil - ) -> NewIssueTableViewController? { - - let storyboard = UIStoryboard(name: NSLocalizedString("NewIssue", comment: ""), bundle: nil) - let viewController = storyboard.instantiateInitialViewController() as? NewIssueTableViewController viewController?.hidesBottomBarWhenPushed = true viewController?.client = client From eff2648ee23349cdf25986ad86650a44da57f5bf Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Fri, 9 Nov 2018 15:55:52 -0500 Subject: [PATCH 07/34] Set signature type by repo owner (GitHawkApp) not template title --- Classes/New Issue/IssueTemplates.swift | 2 +- Classes/Views/Constants.swift | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift index b30378e43..991d102cd 100644 --- a/Classes/New Issue/IssueTemplates.swift +++ b/Classes/New Issue/IssueTemplates.swift @@ -72,7 +72,7 @@ enum IssueTemplateHelper { owner: details.repo.owner, repo: details.repo.name, template: template.template, - signature: template.title == "Bug Report" ? .bugReport : .sentWithGitHawk + signature: details.repo.owner == Constants.GitHawk.owner ? .bugReport : .sentWithGitHawk ) else { assertionFailure("Failed to create NewIssueTableViewController") return diff --git a/Classes/Views/Constants.swift b/Classes/Views/Constants.swift index 7d941ce59..784340c27 100644 --- a/Classes/Views/Constants.swift +++ b/Classes/Views/Constants.swift @@ -10,6 +10,10 @@ import Foundation enum Constants { + enum GitHawk { + static let owner = "GitHawkApp" + static let name = "GitHawk" + } enum Filepaths { static let githubIssue = ".github/ISSUE_TEMPLATE" } From 62c703b0212d62263d16bbcaa3d0821f45218993 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Sat, 10 Nov 2018 18:38:08 -0500 Subject: [PATCH 08/34] Adds regular issue --- Classes/New Issue/IssueTemplates.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift index 991d102cd..06818b686 100644 --- a/Classes/New Issue/IssueTemplates.swift +++ b/Classes/New Issue/IssueTemplates.swift @@ -100,8 +100,15 @@ enum IssueTemplateHelper { details: IssueTemplateDetails) { if sortedTemplates.count > 0 { // Templates exists... + var templates = sortedTemplates + templates.append( + IssueTemplate( + title: NSLocalizedString("Regular Issue", comment: ""), + template: "" + ) + ) let alertView = IssueTemplateHelper.showIssueAlert( - with: sortedTemplates, + with: templates, details: details ) details.viewController.present(alertView, animated: trueUnlessReduceMotionEnabled) From 6291a22864ca3fada21abb50c04c7ec7d1a08ae8 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Sat, 10 Nov 2018 18:48:21 -0500 Subject: [PATCH 09/34] - Made string nil if no template is passed through - Fixed showing error when no template was found --- Classes/New Issue/IssueTemplates.swift | 2 +- Classes/New Issue/NewIssueTableViewController.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift index 06818b686..5533577a7 100644 --- a/Classes/New Issue/IssueTemplates.swift +++ b/Classes/New Issue/IssueTemplates.swift @@ -218,7 +218,7 @@ extension GithubClient { }) } case .error(let error): - Squawk.show(error: error) + if let err = error { Squawk.show(error: err) } completion() } diff --git a/Classes/New Issue/NewIssueTableViewController.swift b/Classes/New Issue/NewIssueTableViewController.swift index a6c576bd2..7dab436a2 100644 --- a/Classes/New Issue/NewIssueTableViewController.swift +++ b/Classes/New Issue/NewIssueTableViewController.swift @@ -51,7 +51,7 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg @IBOutlet var titleField: UITextField! @IBOutlet var bodyField: UITextView! - private var template = "" + private var template: String? private var client: GithubClient! private var owner: String! private var repo: String! @@ -74,7 +74,7 @@ final class NewIssueTableViewController: UITableViewController, UITextFieldDeleg client: GithubClient, owner: String, repo: String, - template: String = "", + template: String? = nil, signature: IssueSignatureType? = nil ) -> NewIssueTableViewController? { From a22e1440a77a9959bde01c709c3a2b3671d76b40 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Sat, 10 Nov 2018 20:50:39 -0500 Subject: [PATCH 10/34] - Cleaned template of details - Moved regex to constants - Refactored regex matching to string extension --- Classes/New Issue/IssueTemplates.swift | 42 +++++++++++++------------- Classes/Utility/String+Regex.swift | 34 +++++++++++++++++++++ Classes/Views/Constants.swift | 9 ++++++ Freetime.xcodeproj/project.pbxproj | 4 +++ 4 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 Classes/Utility/String+Regex.swift diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift index 5533577a7..d3dc411f0 100644 --- a/Classes/New Issue/IssueTemplates.swift +++ b/Classes/New Issue/IssueTemplates.swift @@ -25,26 +25,9 @@ struct IssueTemplateDetails { enum IssueTemplateHelper { - static private func matches( - regex: String, - text: String) -> [String] { - do { - let regex = try NSRegularExpression(pattern: regex) - let str = text as NSString - let results = regex.matches( - in: text, - range: NSMakeRange(0, str.length)) - return results.map { str.substring(with: $0.range)} - - } catch let error as NSError { - print("invalid regex: \(error.localizedDescription)") - return [] - } - } - static func getNameAndDescription(fromTemplatefile file: String) -> (name: String?, about: String?) { - let names = IssueTemplateHelper.matches(regex: "(?<=name:).*", text: file) - let abouts = IssueTemplateHelper.matches(regex: "(?<=about:).*", text: file) + let names = file.matches(regex: Constants.Regex.Template.name) + let abouts = file.matches(regex: Constants.Regex.Template.about) let name = names.first?.trimmingCharacters(in: .whitespaces) let about = abouts.first?.trimmingCharacters(in: .whitespaces) return (name, about) @@ -66,7 +49,6 @@ enum IssueTemplateHelper { title: template.title, style: .default, handler: { _ in - guard let viewController = NewIssueTableViewController.create( client: GithubClient(userSession: details.session), owner: details.repo.owner, @@ -162,7 +144,25 @@ extension GithubClient { case .success(let file): let nameAndDescription = IssueTemplateHelper.getNameAndDescription(fromTemplatefile: file) if let name = nameAndDescription.name { - completion(.success(IssueTemplate(title: name, template: file))) + var cleanedFile = file + + // Remove all template detail text + // ----- + // name: + // about: + // ----- + if let textToClean = file.matches(regex: Constants.Regex.Template.details).first { + if let range = file.range(of: textToClean) { + cleanedFile = file.replacingOccurrences( + of: textToClean, + with: Constants.Strings.emptyString, + options: .literal, + range: range + ) + } + cleanedFile = cleanedFile.trimmingCharacters(in: .whitespacesAndNewlines) + } + completion(.success(IssueTemplate(title: name, template: cleanedFile))) } else { completion(.error(nil)) } diff --git a/Classes/Utility/String+Regex.swift b/Classes/Utility/String+Regex.swift new file mode 100644 index 000000000..44229075a --- /dev/null +++ b/Classes/Utility/String+Regex.swift @@ -0,0 +1,34 @@ +// +// String+Regex.swift +// Freetime +// +// Created by Ehud Adler on 11/10/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import Foundation +import UIKit + + +extension String { + func matches( + regex: String) -> [String] { + do { + let regex = try NSRegularExpression(pattern: regex) + let str = self as NSString + let results = regex.matches( + in: self, + range: NSRange( + location: 0, + length: str.length + ) + ) + return results.map { str.substring(with: $0.range)} + + } catch let error as NSError { + print("invalid regex: \(error.localizedDescription)") + return [] + } + } +} + diff --git a/Classes/Views/Constants.swift b/Classes/Views/Constants.swift index 784340c27..6fbc04f00 100644 --- a/Classes/Views/Constants.swift +++ b/Classes/Views/Constants.swift @@ -10,6 +10,13 @@ import Foundation enum Constants { + enum Regex { + enum Template { + static let name = "(?<=name:).*" + static let about = "(?<=about:).*" + static let details = "([-]{3,})([\\s\\S]*)([-]{3,})" + } + } enum GitHawk { static let owner = "GitHawkApp" static let name = "GitHawk" @@ -64,5 +71,7 @@ enum Constants { static let reviewers = NSLocalizedString("Reviewers", comment: "") static let reviewGitHubAccess = NSLocalizedString("Review GitHub Access", comment: "") static let clear = NSLocalizedString("Clear", comment: "") + static let emptyString = NSLocalizedString("", comment: "") + } } diff --git a/Freetime.xcodeproj/project.pbxproj b/Freetime.xcodeproj/project.pbxproj index c18ada9f6..cf838b826 100644 --- a/Freetime.xcodeproj/project.pbxproj +++ b/Freetime.xcodeproj/project.pbxproj @@ -467,6 +467,7 @@ BDB6AA6B215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA64215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift */; }; BDB6AA762165B8EA009BB73C /* SwitchBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */; }; C0BF305421961E1800A954D4 /* SpinnerTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BF305321961E1800A954D4 /* SpinnerTableCell.swift */; }; + C0BF30562197B83A00A954D4 /* String+Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BF30552197B83A00A954D4 /* String+Regex.swift */; }; C0DEB1DD218E7BFB00B6FEFD /* IssueTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */; }; D8BAD0601FDA0A1A00C41071 /* LabelListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD05F1FDA0A1A00C41071 /* LabelListCell.swift */; }; D8BAD0641FDF221900C41071 /* LabelListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BAD0631FDF221900C41071 /* LabelListView.swift */; }; @@ -1028,6 +1029,7 @@ BDB6AA64215FBC35009BB73C /* GitHubClient+RepositoryBranches.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GitHubClient+RepositoryBranches.swift"; sourceTree = ""; }; BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchBranches.swift; sourceTree = ""; }; C0BF305321961E1800A954D4 /* SpinnerTableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerTableCell.swift; sourceTree = ""; }; + C0BF30552197B83A00A954D4 /* String+Regex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Regex.swift"; sourceTree = ""; }; C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueTemplates.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 = ""; }; @@ -2157,6 +2159,7 @@ 98835BCD1F1965E2005BA24F /* UIDevice+Model.swift */, 98B5A0851F6D0FFE000617D6 /* UINavigationController+Replace.swift */, 299F63DF205DDF6B0015D901 /* UIViewController+Routing.swift */, + C0BF30552197B83A00A954D4 /* String+Regex.swift */, ); path = Utility; sourceTree = ""; @@ -2871,6 +2874,7 @@ 2999972820310F3100995FFD /* IssueMergeContextModel.swift in Sources */, 7509865A2048959D00D1E37A /* RepositoryWebViewController.swift in Sources */, 29CC294B1FF83488006B6DE7 /* IssueCommentModel+Inset.swift in Sources */, + C0BF30562197B83A00A954D4 /* String+Regex.swift in Sources */, 297403D31F1850DC00ABA95A /* IssueAssigneeViewModel.swift in Sources */, 292FCAFD1EDFCC510026635E /* IssueCollapsedBodies.swift in Sources */, 290744B61F250A6800FD9E48 /* IssueCommentAutocomplete.swift in Sources */, From a08002a2759d15cadbd966cce21ae4ea9548c683 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Sun, 11 Nov 2018 09:06:54 -0500 Subject: [PATCH 11/34] Implement review notes --- Classes/New Issue/GithubClient+Template.swift | 130 ++++++++++ Classes/New Issue/IssueTemplateHelper.swift | 114 +++++++++ Classes/New Issue/IssueTemplates.swift | 237 ------------------ Classes/Settings/SettingsViewController.swift | 16 +- Classes/Utility/String+Regex.swift | 6 +- Classes/Views/SpinnerTableCell.swift | 8 +- Freetime.xcodeproj/project.pbxproj | 20 +- 7 files changed, 268 insertions(+), 263 deletions(-) create mode 100644 Classes/New Issue/GithubClient+Template.swift create mode 100644 Classes/New Issue/IssueTemplateHelper.swift delete mode 100644 Classes/New Issue/IssueTemplates.swift diff --git a/Classes/New Issue/GithubClient+Template.swift b/Classes/New Issue/GithubClient+Template.swift new file mode 100644 index 000000000..cd410787b --- /dev/null +++ b/Classes/New Issue/GithubClient+Template.swift @@ -0,0 +1,130 @@ +// +// GithubClient+Template.swift +// Freetime +// +// Created by Ehud Adler on 11/11/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import Foundation +import GitHubAPI +import GitHubSession +import Squawk + +extension GithubClient { + + private func fetchTemplateFile( + repo: RepositoryDetails, + filename: String, + completion: @escaping (Result) -> Void + ) { + + self.fetchFile( + owner: repo.owner, + repo: repo.name, + branch: repo.defaultBranch, + path: "\(Constants.Filepaths.githubIssue)/\(filename)") { (result) in + switch result { + case .success(let file): completion(.success(file)) + case .error(let error): completion(.error(error)) + case .nonUTF8: completion(.error(nil)) + } + } + } + + private func createTemplate( + repo: RepositoryDetails, + filename: String, + completion: @escaping (Result) -> Void + ) { + fetchTemplateFile(repo: repo, filename: filename) { (result) in + switch result { + case .success(let file): + let nameAndDescription = IssueTemplateHelper.getNameAndDescription(fromTemplatefile: file) + if let name = nameAndDescription.name { + var cleanedFile = file + + // Remove all template detail text + // ----- + // name: + // about: + // ----- + if let textToClean = file.matches(regex: Constants.Regex.Template.details).first { + if let range = file.range(of: textToClean) { + cleanedFile = file.replacingOccurrences( + of: textToClean, + with: Constants.Strings.emptyString, + options: .literal, + range: range + ) + } + cleanedFile = cleanedFile.trimmingCharacters(in: .whitespacesAndNewlines) + } + completion(.success(IssueTemplate(title: name, template: cleanedFile))) + } else { + completion(.error(nil)) + } + case .error(let error): + completion(.error(error)) + } + } + } + + func createNewIssue( + repo: RepositoryDetails, + session: GitHubUserSession?, + mainViewController: UIViewController, + delegate: NewIssueTableViewControllerDelegate, + completion: @escaping () -> Void + ) { + + var templates: [IssueTemplate] = [] + + // Create group. + // We need this since we will be making multiple async calls inside a for-loop. + let templateGroup = DispatchGroup() + + let details = IssueTemplateDetails( + repo: repo, + session: session, + viewController: mainViewController, + delegate: delegate + ) + + self.fetchFiles( + owner: repo.owner, + repo: repo.name, + branch: repo.defaultBranch, + path: Constants.Filepaths.githubIssue) { (result) in + switch result { + case .success(let files): + for file in files { + templateGroup.enter() + self.createTemplate(repo: repo, filename: file.name, completion: { (result) in + switch result { + case .success(let template): + templates.append(template) + case .error(let error): + if let err = error { Squawk.show(error: err) } + } + templateGroup.leave() + }) + } + case .error(let error): + if let err = error { Squawk.show(error: err) } + completion() + } + + // Wait for async calls in for-loop to finish up + templateGroup.notify(queue: .main) { + completion() + // Sort lexicographically + let sortedTemplates = templates.sorted(by: {$0.title < $1.title }) + IssueTemplateHelper.present( + withTemplates: sortedTemplates, + details: details + ) + } + } + } +} diff --git a/Classes/New Issue/IssueTemplateHelper.swift b/Classes/New Issue/IssueTemplateHelper.swift new file mode 100644 index 000000000..9240e32c9 --- /dev/null +++ b/Classes/New Issue/IssueTemplateHelper.swift @@ -0,0 +1,114 @@ +// +// IssueTemplates.swift +// Freetime +// +// Created by Ehud Adler on 11/3/18. +// Copyright © 2018 Ryan Nystrom. All rights reserved. +// + +import Foundation +import GitHubAPI +import GitHubSession +import Squawk + +struct IssueTemplate { + let title: String + let template: String +} + +struct IssueTemplateDetails { + let repo: RepositoryDetails + let session: GitHubUserSession? + let viewController: UIViewController + weak var delegate: NewIssueTableViewControllerDelegate? +} + +class IssueTemplateHelper { + + static func getNameAndDescription(fromTemplatefile file: String) -> (name: String?, about: String?) { + let names = file.matches(regex: Constants.Regex.Template.name) + let abouts = file.matches(regex: Constants.Regex.Template.about) + let name = names.first?.trimmingCharacters(in: .whitespaces) + let about = abouts.first?.trimmingCharacters(in: .whitespaces) + return (name, about) + } + + static func showIssueAlert( + with templates: [IssueTemplate], + details: IssueTemplateDetails) -> UIAlertController { + + let alertView = UIAlertController( + title: NSLocalizedString("New Issue", comment: ""), + message: NSLocalizedString("Choose Template", comment: ""), + preferredStyle: .alert + ) + + for template in templates { + alertView.addAction( + UIAlertAction( + title: template.title, + style: .default, + handler: { _ in + guard let viewController = NewIssueTableViewController.create( + client: GithubClient(userSession: details.session), + owner: details.repo.owner, + repo: details.repo.name, + template: template.template, + signature: details.repo.owner == Constants.GitHawk.owner ? .bugReport : .sentWithGitHawk + ) else { + assertionFailure("Failed to create NewIssueTableViewController") + return + } + viewController.delegate = details.delegate + let navController = UINavigationController(rootViewController: viewController) + navController.modalPresentationStyle = .formSheet + details.viewController.present(navController, animated: trueUnlessReduceMotionEnabled) + })) + } + + alertView.addAction( + UIAlertAction( + title: NSLocalizedString("Dismiss", comment: ""), + style: .cancel, + handler: { _ in + alertView.dismiss(animated: trueUnlessReduceMotionEnabled) + }) + ) + return alertView + } + + static func present( + withTemplates sortedTemplates: [IssueTemplate], + details: IssueTemplateDetails) { + if sortedTemplates.count > 0 { + // Templates exists... + var templates = sortedTemplates + templates.append( + IssueTemplate( + title: NSLocalizedString("Regular Issue", comment: ""), + template: "" + ) + ) + let alertView = IssueTemplateHelper.showIssueAlert( + with: templates, + details: details + ) + details.viewController.present(alertView, animated: trueUnlessReduceMotionEnabled) + } else { + // No templates exists, show blank new issue view controller + guard let viewController = NewIssueTableViewController.create( + client: GithubClient(userSession: details.session), + owner: details.repo.owner, + repo: details.repo.name, + signature: .sentWithGitHawk + ) else { + assertionFailure("Failed to create NewIssueTableViewController") + return + } + viewController.delegate = details.delegate + let navController = UINavigationController(rootViewController: viewController) + navController.modalPresentationStyle = .formSheet + details.viewController.present(navController, animated: trueUnlessReduceMotionEnabled) + } + } +} diff --git a/Classes/New Issue/IssueTemplates.swift b/Classes/New Issue/IssueTemplates.swift deleted file mode 100644 index d3dc411f0..000000000 --- a/Classes/New Issue/IssueTemplates.swift +++ /dev/null @@ -1,237 +0,0 @@ -// -// IssueTemplates.swift -// Freetime -// -// Created by Ehud Adler on 11/3/18. -// Copyright © 2018 Ryan Nystrom. All rights reserved. -// - -import Foundation -import GitHubAPI -import GitHubSession -import Squawk - -struct IssueTemplate { - let title: String - let template: String -} - -struct IssueTemplateDetails { - let repo: RepositoryDetails - let session: GitHubUserSession? - let viewController: UIViewController - weak var delegate: NewIssueTableViewControllerDelegate? -} - -enum IssueTemplateHelper { - - static func getNameAndDescription(fromTemplatefile file: String) -> (name: String?, about: String?) { - let names = file.matches(regex: Constants.Regex.Template.name) - let abouts = file.matches(regex: Constants.Regex.Template.about) - let name = names.first?.trimmingCharacters(in: .whitespaces) - let about = abouts.first?.trimmingCharacters(in: .whitespaces) - return (name, about) - } - - static func showIssueAlert( - with templates: [IssueTemplate], - details: IssueTemplateDetails) -> UIAlertController { - - let alertView = UIAlertController( - title: NSLocalizedString("New Issue", comment: ""), - message: NSLocalizedString("Choose Template", comment: ""), - preferredStyle: .alert - ) - - for template in templates { - alertView.addAction( - UIAlertAction( - title: template.title, - style: .default, - handler: { _ in - guard let viewController = NewIssueTableViewController.create( - client: GithubClient(userSession: details.session), - owner: details.repo.owner, - repo: details.repo.name, - template: template.template, - signature: details.repo.owner == Constants.GitHawk.owner ? .bugReport : .sentWithGitHawk - ) else { - assertionFailure("Failed to create NewIssueTableViewController") - return - } - viewController.delegate = details.delegate - let navController = UINavigationController(rootViewController: viewController) - navController.modalPresentationStyle = .formSheet - details.viewController.present(navController, animated: trueUnlessReduceMotionEnabled) - })) - } - - alertView.addAction( - UIAlertAction( - title: NSLocalizedString("Dismiss", comment: ""), - style: .cancel, - handler: { _ in - alertView.dismiss(animated: trueUnlessReduceMotionEnabled) - }) - ) - return alertView - } - - static func present( - withTemplates sortedTemplates: [IssueTemplate], - details: IssueTemplateDetails) { - if sortedTemplates.count > 0 { - // Templates exists... - var templates = sortedTemplates - templates.append( - IssueTemplate( - title: NSLocalizedString("Regular Issue", comment: ""), - template: "" - ) - ) - let alertView = IssueTemplateHelper.showIssueAlert( - with: templates, - details: details - ) - details.viewController.present(alertView, animated: trueUnlessReduceMotionEnabled) - } else { - // No templates exists, show blank new issue view controller - guard let viewController = NewIssueTableViewController.create( - client: GithubClient(userSession: details.session), - owner: details.repo.owner, - repo: details.repo.name, - signature: .sentWithGitHawk - ) else { - assertionFailure("Failed to create NewIssueTableViewController") - return - } - viewController.delegate = details.delegate - let navController = UINavigationController(rootViewController: viewController) - navController.modalPresentationStyle = .formSheet - details.viewController.present(navController, animated: trueUnlessReduceMotionEnabled) - } - } -} - -extension GithubClient { - - private func fetchTemplateFile( - repo: RepositoryDetails, - filename: String, - completion: @escaping (Result) -> Void - ) { - - self.fetchFile( - owner: repo.owner, - repo: repo.name, - branch: repo.defaultBranch, - path: "\(Constants.Filepaths.githubIssue)/\(filename)") { (result) in - switch result { - case .success(let file): completion(.success(file)) - case .error(let error): completion(.error(error)) - case .nonUTF8: completion(.error(nil)) - } - } - } - - private func createTemplate( - repo: RepositoryDetails, - filename: String, - completion: @escaping (Result) -> Void - ) { - fetchTemplateFile(repo: repo, filename: filename) { (result) in - switch result { - case .success(let file): - let nameAndDescription = IssueTemplateHelper.getNameAndDescription(fromTemplatefile: file) - if let name = nameAndDescription.name { - var cleanedFile = file - - // Remove all template detail text - // ----- - // name: - // about: - // ----- - if let textToClean = file.matches(regex: Constants.Regex.Template.details).first { - if let range = file.range(of: textToClean) { - cleanedFile = file.replacingOccurrences( - of: textToClean, - with: Constants.Strings.emptyString, - options: .literal, - range: range - ) - } - cleanedFile = cleanedFile.trimmingCharacters(in: .whitespacesAndNewlines) - } - completion(.success(IssueTemplate(title: name, template: cleanedFile))) - } else { - completion(.error(nil)) - } - case .error(let error): - completion(.error(error)) - } - } - } - - func createNewIssue( - repo: RepositoryDetails, - session: GitHubUserSession?, - mainViewController: UIViewController, - delegate: NewIssueTableViewControllerDelegate, - completion: @escaping () -> Void - ) { - - var templates: [IssueTemplate] = [] - - // Create group. - // We need this since we will be making multiple async calls inside a for-loop. - let templateGroup = DispatchGroup() - - let details = IssueTemplateDetails( - repo: repo, - session: session, - viewController: mainViewController, - delegate: delegate - ) - - self.fetchFiles( - owner: repo.owner, - repo: repo.name, - branch: repo.defaultBranch, - path: Constants.Filepaths.githubIssue) { (result) in - switch result { - case .success(let files): - for file in files { - templateGroup.enter() - self.createTemplate( - repo: repo, - filename: file.name, - completion: { - switch $0 { - case .success(let template): - templates.append(template) - case .error(let error): - if let err = error { - Squawk.show(error: err) - } - } - templateGroup.leave() - }) - } - case .error(let error): - if let err = error { Squawk.show(error: err) } - completion() - } - - // Wait for async calls in for-loop to finish up - templateGroup.notify(queue: .main) { - completion() - // Sort lexicographically - let sortedTemplates = templates.sorted(by: {$0.title < $1.title }) - IssueTemplateHelper.present( - withTemplates: sortedTemplates, - details: details - ) - } - } - } -} diff --git a/Classes/Settings/SettingsViewController.swift b/Classes/Settings/SettingsViewController.swift index ac4a6c588..71e129c45 100644 --- a/Classes/Settings/SettingsViewController.swift +++ b/Classes/Settings/SettingsViewController.swift @@ -154,10 +154,6 @@ NewIssueTableViewControllerDelegate, DefaultReactionDelegate { } func onReportBug() { - showTemplateOptions() - } - - func showTemplateOptions() { let repo = RepositoryDetails( owner: "GitHawkApp", name: "GitHawk", @@ -165,13 +161,13 @@ NewIssueTableViewControllerDelegate, DefaultReactionDelegate { hasIssuesEnabled: true ) - reportBugCell.showSpinner() + reportBugCell.startSpinner() client.createNewIssue( - repo: repo, - session: sessionManager.focusedUserSession, - mainViewController: self, - delegate: self) { - self.reportBugCell.stopSpinner() + repo: repo, + session: sessionManager.focusedUserSession, + mainViewController: self, + delegate: self) { + self.reportBugCell.stopSpinner() } } diff --git a/Classes/Utility/String+Regex.swift b/Classes/Utility/String+Regex.swift index 44229075a..fc8953d3a 100644 --- a/Classes/Utility/String+Regex.swift +++ b/Classes/Utility/String+Regex.swift @@ -11,8 +11,7 @@ import UIKit extension String { - func matches( - regex: String) -> [String] { + func matches(regex: String) -> [String] { do { let regex = try NSRegularExpression(pattern: regex) let str = self as NSString @@ -23,8 +22,7 @@ extension String { length: str.length ) ) - return results.map { str.substring(with: $0.range)} - + return results.map { str.substring(with: $0.range) } } catch let error as NSError { print("invalid regex: \(error.localizedDescription)") return [] diff --git a/Classes/Views/SpinnerTableCell.swift b/Classes/Views/SpinnerTableCell.swift index a096df7f3..69dcf3729 100644 --- a/Classes/Views/SpinnerTableCell.swift +++ b/Classes/Views/SpinnerTableCell.swift @@ -10,8 +10,8 @@ import UIKit final class SpinnerTableCell: StyledTableCell { - let activity = UIActivityIndicatorView(activityIndicatorStyle: .gray) - var indicatorHolder: UITableViewCellAccessoryType? + private let activity = UIActivityIndicatorView(activityIndicatorStyle: .gray) + private var indicatorHolder: UITableViewCellAccessoryType? override func awakeFromNib() { super.awakeFromNib() @@ -23,12 +23,12 @@ final class SpinnerTableCell: StyledTableCell { activity.hidesWhenStopped = true } - public func showSpinner() { + func startSpinner() { indicatorHolder = self.accessoryType self.accessoryType = .none activity.startAnimating() } - public func stopSpinner() { + func stopSpinner() { activity.stopAnimating() self.accessoryType = indicatorHolder ?? .none } diff --git a/Freetime.xcodeproj/project.pbxproj b/Freetime.xcodeproj/project.pbxproj index bf6b41b73..f75e8a012 100644 --- a/Freetime.xcodeproj/project.pbxproj +++ b/Freetime.xcodeproj/project.pbxproj @@ -50,12 +50,12 @@ 291929611F3FD2960012067B /* DiffString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291929601F3FD2960012067B /* DiffString.swift */; }; 291929631F3FF0DA0012067B /* StyledTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291929621F3FF0DA0012067B /* StyledTableCell.swift */; }; 291929671F3FF9C50012067B /* BadgeNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291929661F3FF9C50012067B /* BadgeNotifications.swift */; }; - 291E98832197697400E5EED9 /* ContentWidthUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291E98822197697400E5EED9 /* ContentWidthUtils.swift */; }; - 291E988521976B5600E5EED9 /* ContentWidthUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291E988421976B5600E5EED9 /* ContentWidthUtilsTests.swift */; }; 291E987B21973B8F00E5EED9 /* URLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291E987A21973B8F00E5EED9 /* URLBuilder.swift */; }; 291E987D21973FA700E5EED9 /* URLBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291E987C21973FA700E5EED9 /* URLBuilderTests.swift */; }; 291E987F2197515C00E5EED9 /* UIApplication+ReviewAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291E987E2197515C00E5EED9 /* UIApplication+ReviewAccess.swift */; }; 291E988121975FCB00E5EED9 /* UIApplication+WriteReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291E988021975FCB00E5EED9 /* UIApplication+WriteReview.swift */; }; + 291E98832197697400E5EED9 /* ContentWidthUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291E98822197697400E5EED9 /* ContentWidthUtils.swift */; }; + 291E988521976B5600E5EED9 /* ContentWidthUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291E988421976B5600E5EED9 /* ContentWidthUtilsTests.swift */; }; 29242810210A50BE001F5980 /* SpacerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2924280F210A50BE001F5980 /* SpacerModel.swift */; }; 29242812210A5148001F5980 /* SpacerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29242811210A5148001F5980 /* SpacerCell.swift */; }; 29242814210A51B5001F5980 /* SpacerSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29242813210A51B5001F5980 /* SpacerSectionController.swift */; }; @@ -477,7 +477,8 @@ BDB6AA762165B8EA009BB73C /* SwitchBranches.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */; }; C0BF305421961E1800A954D4 /* SpinnerTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BF305321961E1800A954D4 /* SpinnerTableCell.swift */; }; C0BF30562197B83A00A954D4 /* String+Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BF30552197B83A00A954D4 /* String+Regex.swift */; }; - C0DEB1DD218E7BFB00B6FEFD /* IssueTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */; }; + C0BF305821986CFA00A954D4 /* GithubClient+Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BF305721986CFA00A954D4 /* GithubClient+Template.swift */; }; + C0DEB1DD218E7BFB00B6FEFD /* IssueTemplateHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEB1DC218E7BFB00B6FEFD /* IssueTemplateHelper.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 */; }; @@ -603,12 +604,12 @@ 291929601F3FD2960012067B /* DiffString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffString.swift; sourceTree = ""; }; 291929621F3FF0DA0012067B /* StyledTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledTableCell.swift; sourceTree = ""; }; 291929661F3FF9C50012067B /* BadgeNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeNotifications.swift; sourceTree = ""; }; - 291E98822197697400E5EED9 /* ContentWidthUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWidthUtils.swift; sourceTree = ""; }; - 291E988421976B5600E5EED9 /* ContentWidthUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWidthUtilsTests.swift; sourceTree = ""; }; 291E987A21973B8F00E5EED9 /* URLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBuilder.swift; sourceTree = ""; }; 291E987C21973FA700E5EED9 /* URLBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBuilderTests.swift; sourceTree = ""; }; 291E987E2197515C00E5EED9 /* UIApplication+ReviewAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+ReviewAccess.swift"; sourceTree = ""; }; 291E988021975FCB00E5EED9 /* UIApplication+WriteReview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+WriteReview.swift"; sourceTree = ""; }; + 291E98822197697400E5EED9 /* ContentWidthUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWidthUtils.swift; sourceTree = ""; }; + 291E988421976B5600E5EED9 /* ContentWidthUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWidthUtilsTests.swift; sourceTree = ""; }; 2924280F210A50BE001F5980 /* SpacerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacerModel.swift; sourceTree = ""; }; 29242811210A5148001F5980 /* SpacerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacerCell.swift; sourceTree = ""; }; 29242813210A51B5001F5980 /* SpacerSectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacerSectionController.swift; sourceTree = ""; }; @@ -1048,7 +1049,8 @@ BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchBranches.swift; sourceTree = ""; }; C0BF305321961E1800A954D4 /* SpinnerTableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerTableCell.swift; sourceTree = ""; }; C0BF30552197B83A00A954D4 /* String+Regex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Regex.swift"; sourceTree = ""; }; - C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueTemplates.swift; sourceTree = ""; }; + C0BF305721986CFA00A954D4 /* GithubClient+Template.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GithubClient+Template.swift"; sourceTree = ""; }; + C0DEB1DC218E7BFB00B6FEFD /* IssueTemplateHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueTemplateHelper.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 = ""; }; @@ -2204,7 +2206,8 @@ children = ( 98B5A0811F6C73D6000617D6 /* NewIssue.storyboard */, 98647DF21F758CCF00A4DE7A /* NewIssueTableViewController.swift */, - C0DEB1DC218E7BFB00B6FEFD /* IssueTemplates.swift */, + C0DEB1DC218E7BFB00B6FEFD /* IssueTemplateHelper.swift */, + C0BF305721986CFA00A954D4 /* GithubClient+Template.swift */, ); path = "New Issue"; sourceTree = ""; @@ -2955,6 +2958,7 @@ 2930988F211F32D100E1178B /* DefaultReactionDetailController.swift in Sources */, 29C8F9AD208C02860075931C /* LoadMoreSectionController2.swift in Sources */, 292CD3BB1F0AF28F00D3D57B /* IssueDiffHunkModel.swift in Sources */, + C0BF305821986CFA00A954D4 /* GithubClient+Template.swift in Sources */, 292CD3BF1F0AF3C000D3D57B /* IssueDiffHunkPathCell.swift in Sources */, DCA5ED161FAEE91F0072F074 /* BookmarkViewModel.swift in Sources */, 2924C18D20D5B3DD00FCFCFF /* LabelsViewController.swift in Sources */, @@ -2985,7 +2989,7 @@ 29F3A18820CBFF8700645CB7 /* CreateNotificationTitle.swift in Sources */, 292EB08D1F1FF58D0046865D /* IssueMilestoneEventModel.swift in Sources */, 292EB0911F1FF72D0046865D /* IssueMilestoneEventSectionController.swift in Sources */, - C0DEB1DD218E7BFB00B6FEFD /* IssueTemplates.swift in Sources */, + C0DEB1DD218E7BFB00B6FEFD /* IssueTemplateHelper.swift in Sources */, DCA5ED141FAEE8030072F074 /* Bookmark.swift in Sources */, 299F4A89204CEDDC004BA4F0 /* Client+AccessToken.swift in Sources */, 2931892F1F539C0E00EF0911 /* IssueMilestoneSectionController.swift in Sources */, From f9129aa075858427db0e018aed61036a1c908171 Mon Sep 17 00:00:00 2001 From: Ehud Adler Date: Sun, 11 Nov 2018 09:51:37 -0500 Subject: [PATCH 12/34] Implemented review comments --- Classes/New Issue/GithubClient+Template.swift | 2 +- Classes/New Issue/IssueTemplateHelper.swift | 3 ++- Classes/Repository/RepositoryViewController.swift | 2 +- Classes/Settings/Settings.storyboard | 2 +- Classes/Utility/String+Regex.swift | 2 +- Classes/Views/Constants.swift | 2 -- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Classes/New Issue/GithubClient+Template.swift b/Classes/New Issue/GithubClient+Template.swift index cd410787b..41ba0fd58 100644 --- a/Classes/New Issue/GithubClient+Template.swift +++ b/Classes/New Issue/GithubClient+Template.swift @@ -53,7 +53,7 @@ extension GithubClient { if let range = file.range(of: textToClean) { cleanedFile = file.replacingOccurrences( of: textToClean, - with: Constants.Strings.emptyString, + with: "", options: .literal, range: range ) diff --git a/Classes/New Issue/IssueTemplateHelper.swift b/Classes/New Issue/IssueTemplateHelper.swift index 9240e32c9..caf8a08dc 100644 --- a/Classes/New Issue/IssueTemplateHelper.swift +++ b/Classes/New Issue/IssueTemplateHelper.swift @@ -37,10 +37,11 @@ class IssueTemplateHelper { with templates: [IssueTemplate], details: IssueTemplateDetails) -> UIAlertController { + let alertView = UIAlertController( title: NSLocalizedString("New Issue", comment: ""), message: NSLocalizedString("Choose Template", comment: ""), - preferredStyle: .alert + preferredStyle: .actionSheet ) for template in templates { diff --git a/Classes/Repository/RepositoryViewController.swift b/Classes/Repository/RepositoryViewController.swift index 5ca793906..d25d111c3 100644 --- a/Classes/Repository/RepositoryViewController.swift +++ b/Classes/Repository/RepositoryViewController.swift @@ -147,7 +147,7 @@ ContextMenuDelegate { } func newIssueAction() -> UIAlertAction? { - let action = UIAlertAction(title: "New Issue", style: .default) { _ in + let action = UIAlertAction(title: NSLocalizedString("New Issue", comment: ""), style: .default) { _ in self.client.createNewIssue( repo: self.repo, session: nil, diff --git a/Classes/Settings/Settings.storyboard b/Classes/Settings/Settings.storyboard index ad06493b7..4c3621fca 100644 --- a/Classes/Settings/Settings.storyboard +++ b/Classes/Settings/Settings.storyboard @@ -132,7 +132,7 @@ -