Merge branch 'master' of github.com:rnystrom/GitHawk

This commit is contained in:
Ryan Nystrom
2017-09-25 10:33:29 -04:00
592 changed files with 5071 additions and 2522 deletions

1
.gitignore vendored
View File

@@ -68,3 +68,4 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
node_modules

29
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,29 @@
## Contributing to GitHawk
This project is continuing to develop rapidly and new features are constantly being added. Pull Requests **are** accepted but please note that `master` is constantly changing and your submissions may go stale quick. Be warned!
### Setup
In order to compile and run GitHawk locally you will need to provide your own client ID and secret keys from GitHub. You can get these by [registering](https://github.com/settings/applications/new) a new OAuth application. The "Authorization callback URL" must be set to `freetime://`, otherwise you're free to fill it in yourself!
Once you have your client ID and secret you can populate these values into the [Secrets.swift](https://github.com/rnystrom/GitHawk/blob/master/Classes/Other/Secrets.swift) file. You should **never** commit these changes!
Finally you will also need to install `apollo-codegen`, this is a command line tool which is required to create the GraphQL models! This can be done easily by running `npm install`.
### Pull Requests
All changes should be made off of the latest version of `master`. If a Pull Request is still being worked on then please make this obvious by including "WIP" in the title!
Pull Requests should have a suitable amount of testing done to ensure it doesn't break the app, and we do appreciate new unit tests being created although this isn't currently enforced in the aim of releasing faster!
### Issues
We use [GitHub issues](https://github.com/rnystrom/GitHawk/issues) in order to track feature requests and bugs! You can raise bugs through the app itself from the Settings page and then tapping "Report a Bug" - this will also include some device specific information to make fixing those bugs a little bit easier!
### Beta Testing
We use [TestFlight](https://itunes.apple.com/gb/app/testflight/id899247664?mt=8) in order to distribute beta versions of this application. If you're interested in helping then DM [@GitHawk](https://twitter.com/GitHawk) on Twitter with your email address and you'll be added to the list!
### License
By contributing to `GitHawk`, you agree that your contributions will be licensed under the terms found in our [LICENSE](https://github.com/rnystrom/GitHawk/blob/master/LICENSE) file. You understand that this project is released, for free, on the iOS App Store.

View File

@@ -247,7 +247,7 @@ IssueTextActionsViewDelegate {
}
func onMore(sender: UIBarButtonItem) {
let alert = UIAlertController()
let alert = UIAlertController.configured(preferredStyle: .actionSheet)
if let close = closeAction() {
alert.addAction(close)

View File

@@ -51,13 +51,13 @@ final class LoginSplashViewController: UIViewController, GithubSessionListener {
// MARK: Private API
@IBAction func onSignInButton(_ sender: Any) {
let safari = SFSafariViewController(url: loginURL)
safariController = safari
present(safari, animated: true)
guard let safariController = try? SFSafariViewController.configured(with: loginURL) else { return }
self.safariController = safariController
present(safariController, animated: true)
}
@IBAction func onPersonalAccessTokenButton(_ sender: Any) {
let alert = UIAlertController(
let alert = UIAlertController.configured(
title: NSLocalizedString("Personal Access Token", comment: ""),
message: NSLocalizedString("Sign in with a Personal Access Token with both repo and user scopes.", comment: ""),
preferredStyle: .alert
@@ -92,7 +92,7 @@ final class LoginSplashViewController: UIViewController, GithubSessionListener {
private func handleError() {
state = .idle
let alert = UIAlertController(
let alert = UIAlertController.configured(
title: NSLocalizedString("Error", comment: ""),
message: NSLocalizedString("There was an error signing in to GitHub. Please try again.", comment: ""),
preferredStyle: .alert

View File

@@ -17,7 +17,7 @@ func NavigateToNotificationContent(
switch object.identifier {
case .hash(let hash):
let url = URL(string: "https://github.com/\(object.owner)/\(object.repo)/commit/\(hash)")!
return SFSafariViewController(url: url)
return try! SFSafariViewController.configured(with: url)
case .number(let number):
let model = IssueDetailsModel(owner: object.owner, repo: object.repo, number: number)
let controller = IssuesViewController(

View File

@@ -109,7 +109,7 @@ PrimaryViewController {
}
@objc private func onMarkAll(sender: UIBarButtonItem) {
let alert = UIAlertController(
let alert = UIAlertController.configured(
title: NSLocalizedString("Notifications", comment: ""),
message: "Mark all notifications as read?",
preferredStyle: .alert

View File

@@ -80,21 +80,20 @@ class RepositoryViewController: TabmanViewController, PageboyViewControllerDataS
func safariAction() -> UIAlertAction {
return UIAlertAction(title: NSLocalizedString("Open in Safari", comment: ""), style: .default) { [weak self] _ in
guard let strongSelf = self else { return }
let controller = SFSafariViewController(url: strongSelf.repoUrl)
strongSelf.present(controller, animated: true)
strongSelf.presentSafari(url: strongSelf.repoUrl)
}
}
func viewOwnerAction() -> UIAlertAction {
return UIAlertAction(title: NSLocalizedString("View Owner's Profile", comment: ""), style: .default) { [weak self] _ in
guard let strongSelf = self else { return }
let controller = SFSafariViewController(url: URL(string: "https://github.com/\(strongSelf.repo.owner)")!)
strongSelf.present(controller, animated: true)
let url = URL(string: "https://github.com/\(strongSelf.repo.owner)")!
strongSelf.presentSafari(url: url)
}
}
func onMore(sender: UIBarButtonItem) {
let alert = UIAlertController()
let alert = UIAlertController.configured(preferredStyle: .actionSheet)
alert.addAction(shareAction(sender: sender))
alert.addAction(safariAction())
alert.addAction(viewOwnerAction())

View File

@@ -22,7 +22,7 @@ final class SettingsAccountsViewController: UITableViewController, GithubSession
// MARK: Private API
@IBAction func onAdd(_ sender: Any) {
let alert = UIAlertController(
let alert = UIAlertController.configured(
title: NSLocalizedString("Add Account", comment: ""),
message: NSLocalizedString("To sign in with another account, please add a new Personal Access Token with user and repo scopes.", comment: ""),
preferredStyle: .alert
@@ -53,7 +53,7 @@ final class SettingsAccountsViewController: UITableViewController, GithubSession
}
private func handleError() {
let alert = UIAlertController(
let alert = UIAlertController.configured(
title: NSLocalizedString("Error", comment: ""),
message: NSLocalizedString("There was an error adding another account. Please try again.", comment: ""),
preferredStyle: .alert

View File

@@ -42,6 +42,7 @@ final class SettingsViewController: UITableViewController {
signatureSwitch.isOn = Signature.enabled
updateBadge()
style()
NotificationCenter.default.addObserver(
self,
@@ -90,8 +91,7 @@ final class SettingsViewController: UITableViewController {
func onReviewAccess() {
guard let url = URL(string: "https://github.com/settings/connections/applications/\(GithubAPI.clientID)")
else { fatalError("Should always create GitHub issue URL") }
let safari = SFSafariViewController(url: url)
present(safari, animated: true)
presentSafari(url: url)
}
func onReportBug() {
@@ -100,15 +100,13 @@ final class SettingsViewController: UITableViewController {
guard let url = URL(string: "https://github.com/rnystrom/GitHawk/issues/new?body=\(template)")
else { fatalError("Should always create GitHub issue URL") }
let safari = SFSafariViewController(url: url)
present(safari, animated: true)
presentSafari(url: url)
}
func onViewSource() {
guard let url = URL(string: "https://github.com/rnystrom/GitHawk/")
else { fatalError("Should always create GitHub URL") }
let safari = SFSafariViewController(url: url)
present(safari, animated: true)
presentSafari(url: url)
}
func onSignOut() {
@@ -120,7 +118,7 @@ final class SettingsViewController: UITableViewController {
let title = NSLocalizedString("Are you sure?", comment: "")
let message = NSLocalizedString("All of your accounts will be signed out. Do you want to continue?", comment: "")
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let alert = UIAlertController.configured(title: title, message: message, preferredStyle: .alert)
alert.addAction(cancelAction)
alert.addAction(signoutAction)
@@ -199,6 +197,11 @@ final class SettingsViewController: UITableViewController {
apiStatusLabel.textColor = color
}
}
private func style() {
[backgroundFetchSwitch, markReadSwitch, signatureSwitch]
.forEach({ $0.onTintColor = Styles.Colors.Green.medium.color })
}
@IBAction func onSignature(_ sender: Any) {
Signature.enabled = signatureSwitch.isOn

View File

@@ -11,5 +11,5 @@ import SafariServices
func CreateProfileViewController(login: String) -> UIViewController {
let url = URL(string: "https://github.com/\(login)")!
return SFSafariViewController(url: url)
return try! SFSafariViewController.configured(with: url)
}

View File

@@ -11,10 +11,19 @@ import UIKit
extension UIViewController {
func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let alert = UIAlertController.configured(title: title, message: message, preferredStyle: .alert)
let action = UIAlertAction(title: Strings.ok, style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}
extension UIAlertController {
static func configured(title: String? = nil, message: String? = nil, preferredStyle: UIAlertControllerStyle) -> UIAlertController {
let alertController = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
alertController.view.tintColor = Styles.Colors.Blue.medium.color
return alertController
}
}

View File

@@ -12,16 +12,8 @@ import SafariServices
extension UIViewController {
func presentSafari(url: URL) {
let http = "http"
let schemedURL: URL
// handles http and https
if url.scheme?.hasPrefix(http) == true {
schemedURL = url
} else {
guard let u = URL(string: http + "://" + url.absoluteString) else { return }
schemedURL = u
}
present(SFSafariViewController(url: schemedURL), animated: true)
guard let safariViewController = try? SFSafariViewController.configured(with: url) else { return }
present(safariViewController, animated: true)
}
func presentProfile(login: String) {
@@ -44,3 +36,28 @@ extension UIViewController {
}
}
extension SFSafariViewController {
static func configured(with url: URL) throws -> SFSafariViewController {
let http = "http"
let schemedURL: URL
// handles http and https
if url.scheme?.hasPrefix(http) == true {
schemedURL = url
} else {
guard let u = URL(string: http + "://" + url.absoluteString) else { throw URL.Error.failedToCreateURL }
schemedURL = u
}
let safariViewController = SFSafariViewController(url: schemedURL)
safariViewController.preferredControlTintColor = Styles.Colors.Blue.medium.color
return safariViewController
}
}
extension URL {
enum Error: Swift.Error {
case failedToCreateURL
}
}

View File

@@ -581,7 +581,6 @@
29A195061EC7601000C3E289 /* Localizable.stringsdict */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = "<group>"; };
29A195091EC78B4800C3E289 /* NotificationType+Icon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NotificationType+Icon.swift"; sourceTree = "<group>"; };
29A1950B1EC7901400C3E289 /* NotificationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationType.swift; sourceTree = "<group>"; };
29A1950D1EC791BD00C3E289 /* LabelPopover.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = LabelPopover.playground; sourceTree = "<group>"; };
29A195101EC7AC9500C3E289 /* NotificationRepoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRepoCell.swift; sourceTree = "<group>"; };
29A4768D1ED07A23005D0953 /* DateDetailsFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateDetailsFormatter.swift; sourceTree = "<group>"; };
29A4769F1ED0E6C6005D0953 /* UIColor+Overlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Overlay.swift"; sourceTree = "<group>"; };
@@ -633,8 +632,6 @@
29EE443E1F19B2D300B05ED3 /* WriteButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WriteButton.swift; sourceTree = "<group>"; };
29EE44451F19D5C100B05ED3 /* GithubClient+Issues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GithubClient+Issues.swift"; sourceTree = "<group>"; };
29EE44491F19D85800B05ED3 /* ShowErrorStatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShowErrorStatusBar.swift; sourceTree = "<group>"; };
29F63FD01EC530BD007F55E4 /* Github API.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = "Github API.playground"; sourceTree = "<group>"; };
29F63FD11EC530E7007F55E4 /* TextViews.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = TextViews.playground; sourceTree = "<group>"; };
29F7F05B1F2A751B00F6075D /* IssueResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueResult.swift; sourceTree = "<group>"; };
29F7F05E1F2A839100F6075D /* IssueNeckLoadSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueNeckLoadSectionController.swift; sourceTree = "<group>"; };
29F7F0601F2A83AA00F6075D /* IssueNeckLoadCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueNeckLoadCell.swift; sourceTree = "<group>"; };
@@ -1085,15 +1082,12 @@
297AE8571EC0D5C100B44A1F /* Classes */,
CF4CC0BFE456879DD6DBC714 /* Frameworks */,
297AE84B1EC0D58A00B44A1F /* FreetimeTests */,
29F63FD01EC530BD007F55E4 /* Github API.playground */,
292FCB2A1EE0546E0026635E /* gql */,
29A1950D1EC791BD00C3E289 /* LabelPopover.playground */,
33A455DB21DE4BF7CF5B8C00 /* Pods */,
297AE8351EC0D58A00B44A1F /* Products */,
297AE86D1EC0D5C200B44A1F /* Resources */,
296CD8231F014130001190B9 /* Sample */,
984D9CA81F212ADF00ECEA7F /* Settings.bundle */,
29F63FD11EC530E7007F55E4 /* TextViews.playground */,
);
sourceTree = "<group>";
};
@@ -1687,7 +1681,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "APOLLO_FRAMEWORK_PATH=\"$(eval find $FRAMEWORK_SEARCH_PATHS -name \"Apollo.framework\" -maxdepth 1)\"\n\nif [ -z \"$APOLLO_FRAMEWORK_PATH\" ]; then\necho \"error: Couldn't find Apollo.framework in FRAMEWORK_SEARCH_PATHS; make sure to add the framework to your project.\"\nexit 1\nfi\n\ncd \"${SRCROOT}/gql\"\nTEMP_FILE=$(mktemp)\n\n[[ -f API.swift ]] || touch API.swift # ensure sure file exists\n\n$APOLLO_FRAMEWORK_PATH/check-and-run-apollo-codegen.sh generate $(find . -name '*.graphql') --schema schema.json --output $TEMP_FILE\n\ncmp API.swift $TEMP_FILE || cp $TEMP_FILE API.swift";
shellScript = "PATH=\"$(npm bin):$PATH\"\n\nAPOLLO_FRAMEWORK_PATH=\"$(eval find $FRAMEWORK_SEARCH_PATHS -name \"Apollo.framework\" -maxdepth 1)\"\n\nif [ -z \"$APOLLO_FRAMEWORK_PATH\" ]; then\necho \"error: Couldn't find Apollo.framework in FRAMEWORK_SEARCH_PATHS; make sure to add the framework to your project.\"\nexit 1\nfi\n\ncd \"${SRCROOT}/gql\"\nTEMP_FILE=$(mktemp)\n\n[[ -f API.swift ]] || touch API.swift # ensure sure file exists\n\n$APOLLO_FRAMEWORK_PATH/check-and-run-apollo-codegen.sh generate $(find . -name '*.graphql') --schema schema.json --output $TEMP_FILE\n\ncmp API.swift $TEMP_FILE || cp $TEMP_FILE API.swift";
};
2CC988A6FBEAF3F744A53C18 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
@@ -1704,7 +1698,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
569FA08158547B98DF94DD42 /* [CP] Embed Pods Frameworks */ = {
@@ -1768,7 +1762,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
E002D18DA7EA57D5E53A2BBE /* [CP] Copy Pods Resources */ = {

View File

@@ -1,15 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:SlackTextViewController/SlackTextViewController/SlackTextViewController.xcodeproj">
</FileRef>
<FileRef
location = "group:SwipeCellKit/SwipeCellKit.xcodeproj">
</FileRef>
<FileRef
location = "group:MMMarkdown/MMMarkdown.xcodeproj">
</FileRef>
<FileRef
location = "group:Freetime.xcodeproj">
</FileRef>

View File

@@ -0,0 +1,16 @@
@version = "0.5.5"
Pod::Spec.new do |s|
s.name = "MMMarkdown"
s.version = @version
s.summary = "An Objective-C static library for converting Markdown to HTML."
s.homepage = "https://github.com/mdiep/MMMarkdown"
s.license = { :type => 'MIT', :file => 'LICENSE.md' }
s.author = { "Matt Diephouse" => "matt@diephouse.com" }
s.source = { :git => "https://github.com/mdiep/MMMarkdown.git", :tag => "#{s.version}" }
s.platform = :ios, "8.0"
s.requires_arc = true
s.source_files = 'Source/**/*.{h,m}'
end

Some files were not shown because too many files have changed in this diff Show More