Skip to content

Commit

Permalink
Revamp UI
Browse files Browse the repository at this point in the history
Merge pull request #9 from Cykelero/revamp-ui
  • Loading branch information
DivineDominion authored Nov 24, 2024
2 parents db69386 + 8482c4f commit 3f444d8
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 302 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
Your app will crash one day. Be prepared to collect crash data automatically, because not every user is a techno wizard capable of sending you `.crash` files from the built-in Console app.

<div align="center">
<a href="assets/reporter-light.png"><img src="assets/reporter-light.png" width="300"/></a>
<a href="assets/reporter-dark.png"><img src="assets/reporter-dark.png" width="300"/></a>
<a href="assets/reporter-light.png"><img src="assets/reporter-light.png" width="300" height="224"/></a>
<a href="assets/reporter-dark.png"><img src="assets/reporter-dark.png" width="300" height="224"/></a>
</div>

## Requirements
Expand Down Expand Up @@ -58,6 +58,7 @@ The crash reporter framework will perform a HTTP POST request:

- The `User-Agent` metadata is set to `"\(APP_NAME)-\(VERSION)"` if the values are found in the app's bundle, e.g. `"Sherlock-2.0"`.
- The `userEmail` variable is either left out or set to the email entered by the user.
- The `userProvidedDetails` variable is either left out or set to the details entered by the user.
- The `crashlog` variable is set to the contents of the `.crash` file the user submits.
- The server response will be ignored.

Expand Down
8 changes: 6 additions & 2 deletions Sources/CrashReporter/CrashReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ public final class CrashReporter {
if shouldSendCrashLogsAutomatically && alwaysShowCrashReporterWindow == false {
let emailSetting = EmailAddressSetting(isVisible: false, userDefaults: self.userDefaults, emailAddressKey: self.defaultsKeys.emailAddressKey)
let emailAddress = collectEmailAddress ? emailSetting.emailAddress : nil
send(emailAddress: emailAddress, crashLogText: crashLog.content)
send(emailAddress: emailAddress, userProvidedDetails: nil, crashLogText: crashLog.content)
} else {
runCrashReporterWindow(
appName: appName,
crashLog: crashLog,
displayAsModal: displayCrashReporterWindowAsModal,
hideEmailCollection: !collectEmailAddress,
Expand Down Expand Up @@ -158,6 +159,7 @@ public final class CrashReporter {
internal var crashReportWindowController: CrashReportWindowController?

internal func runCrashReporterWindow(
appName: String,
crashLog: CrashLog,
displayAsModal: Bool,
hideEmailCollection: Bool,
Expand All @@ -172,6 +174,7 @@ public final class CrashReporter {
sendCrashLogsAutomaticallyKey: self.defaultsKeys.sendCrashLogsAutomaticallyKey)

self.crashReportWindowController = CrashReportWindowController(
appName: appName,
crashLogText: crashLog.content,
// Produces a retain cycle that we'll break when the window closes:
crashLogSender: self,
Expand Down Expand Up @@ -212,7 +215,7 @@ public final class CrashReporter {
// MARK: - SendsCrashLog

extension CrashReporter: SendsCrashLog {
internal func send(emailAddress: String?, crashLogText: String) {
internal func send(emailAddress: String?, userProvidedDetails: String?, crashLogText: String) {
var request = URLRequest(url: self.crashReporterURL)
request.httpMethod = "POST"

Expand All @@ -223,6 +226,7 @@ extension CrashReporter: SendsCrashLog {

let form: [String : String?] = [
"userEmail" : emailAddress,
"userProvidedDetails": userProvidedDetails,
"crashlog" : crashLogText
]
// See <https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4> for a specification.
Expand Down
102 changes: 40 additions & 62 deletions Sources/CrashReporter/UI/CrashReportWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
import AppKit

protocol SendsCrashLog {
func send(emailAddress: String?, crashLogText: String)
func send(emailAddress: String?, userProvidedDetails: String?, crashLogText: String)
}

final class CrashReportWindowController: NSWindowController, NSWindowDelegate {

convenience init(
appName: String,
crashLogText: String,
crashLogSender: SendsCrashLog,
privacyPolicyURL: URL,
Expand All @@ -37,10 +38,26 @@ final class CrashReportWindowController: NSWindowController, NSWindowDelegate {
window?.center()
window?.delegate = self

window?.title = "\(appName) Crash Reporter"
titleLabel.stringValue = "\(appName) quit unexpectedly."
crashLogContainerView.isHidden = true

updateCrashLogText()
updateCollectEmailVisibility()
updateAutomaticallySendCrashLogVisibility()
updateButtonStates()

if let diagnosticsReporterURL = NSWorkspace.shared.urlForApplication(
withBundleIdentifier: "com.apple.DiagnosticsReporter")
{
let diagnosticsReporterIcon = NSWorkspace.shared.icon(
forFile: diagnosticsReporterURL.path)
diagnosticsReporterIcon.size = CGSize(width: 64, height: 64)

warningImageView.image = diagnosticsReporterIcon
} else {
warningImageView.imageAlignment = .alignTopRight
}
}

var onWindowWillClose: ((NSWindow?) -> Void)?
Expand All @@ -59,45 +76,17 @@ final class CrashReportWindowController: NSWindowController, NSWindowDelegate {
}
}

@IBOutlet weak var collectEmailContainerView: NSView!
@IBOutlet weak var emailAddressTitleLabel: NSTextField!
@IBOutlet weak var emailAddressLabel: NSTextField!
@IBOutlet weak var emailAddressTextField: NSTextField!

lazy var hideCollectEmailConstraint: NSLayoutConstraint = {
let containerView = collectEmailContainerView!
let constraint = NSLayoutConstraint(
item: containerView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .height,
multiplier: 1,
constant: 0)
constraint.priority = .required
return constraint
}()
@IBOutlet var warningImageView: NSImageView!
@IBOutlet var titleLabel: NSTextField!
@IBOutlet var bodyLabel: NSTextField!

@IBOutlet weak var collectEmailContainerView: NSView!
@IBOutlet weak var crashLogContainerView: NSView!
@IBOutlet weak var sendAutomaticallyContainerView: NSView!
@IBOutlet weak var sendAutomaticallyCheckbox: NSButton!
@IBOutlet weak var sendAutomaticallyLabel: NSTextField!

lazy var hideSendAutomaticallyConstraint: NSLayoutConstraint = {
let containerView = sendAutomaticallyContainerView!
let constraint = NSLayoutConstraint(
item: containerView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .height,
multiplier: 1,
constant: 0)
constraint.priority = .required
return constraint
}()

@IBOutlet var sendCrashLogButton: NSButton!
@IBOutlet var dontSendButton: NSButton!
@IBOutlet var toggleCrashLogButton: NSButton!

private func updateCrashLogText() {
guard let textView = self.textView else { return }
Expand All @@ -110,35 +99,16 @@ final class CrashReportWindowController: NSWindowController, NSWindowDelegate {
}

private func updateCollectEmailVisibility() {
let isDisabled = self.hideCollectEmail
emailAddressTitleLabel?.isHidden = isDisabled
emailAddressLabel?.isHidden = isDisabled
emailAddressTextField?.isEnabled = !isDisabled
emailAddressTextField?.isHidden = isDisabled

if isDisabled {
if !collectEmailContainerView.constraints.contains(hideCollectEmailConstraint) {
collectEmailContainerView.addConstraint(hideCollectEmailConstraint)
}
} else {
collectEmailContainerView.removeConstraint(hideCollectEmailConstraint)
}
collectEmailContainerView.isHidden = self.hideCollectEmail
bodyLabel.stringValue =
"Help us fix crashes by submitting this crash report."
+ (self.hideCollectEmail
? ""
: " You can include your email address if you agree to being contacted for more details.")
}

private func updateAutomaticallySendCrashLogVisibility() {
let isDisabled = self.hideAutomaticallySend
sendAutomaticallyCheckbox?.isEnabled = !isDisabled
sendAutomaticallyCheckbox?.isHidden = isDisabled
sendAutomaticallyLabel?.isHidden = isDisabled

if isDisabled {
if !sendAutomaticallyContainerView.constraints.contains(hideSendAutomaticallyConstraint)
{
sendAutomaticallyContainerView.addConstraint(hideSendAutomaticallyConstraint)
}
} else {
sendAutomaticallyContainerView.removeConstraint(hideSendAutomaticallyConstraint)
}
sendAutomaticallyContainerView.isHidden = self.hideAutomaticallySend
}

// MARK: Model
Expand Down Expand Up @@ -182,6 +152,8 @@ final class CrashReportWindowController: NSWindowController, NSWindowDelegate {
collectEmailSetting.emailAddress = newValue
}
}

@objc dynamic var userProvidedDetails = ""

internal var privacyPolicyURL: URL?

Expand Down Expand Up @@ -217,7 +189,7 @@ final class CrashReportWindowController: NSWindowController, NSWindowDelegate {
{

let emailAddress = self.collectEmailSetting.isVisible ? self.emailAddress : nil
crashLogSender.send(emailAddress: emailAddress, crashLogText: crashLogText)
crashLogSender.send(emailAddress: emailAddress, userProvidedDetails: userProvidedDetails, crashLogText: crashLogText)
}

close()
Expand All @@ -238,4 +210,10 @@ final class CrashReportWindowController: NSWindowController, NSWindowDelegate {
guard let privacyPolicyURL = self.privacyPolicyURL else { return }
NSWorkspace.shared.open(privacyPolicyURL)
}

@IBAction func toggleCrashLog(_ sender: Any?) {
crashLogContainerView.isHidden = !crashLogContainerView.isHidden
toggleCrashLogButton.title =
crashLogContainerView.isHidden ? "Show Details" : "Hide Details"
}
}
Loading

0 comments on commit 3f444d8

Please sign in to comment.