| 1 | import UIKit |
| 2 | |
| 3 | enum Crash { |
| 4 | private static var offered = false |
| 5 | |
| 6 | static func start() { |
| 7 | guard let folder = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { return } |
| 8 | let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "development" |
| 9 | _ = sb_crash_start(folder.appendingPathComponent("crash.txt").path, build, "iOS \(UIDevice.current.systemVersion)") |
| 10 | } |
| 11 | |
| 12 | static func offer(from host: UIViewController) { |
| 13 | guard !offered, let saved = sb_crash_report() else { return } |
| 14 | let report = String(cString: saved) |
| 15 | sb_string_free(saved) |
| 16 | offered = true |
| 17 | let alert = UIAlertController( |
| 18 | title: "Snowbound quit unexpectedly", message: "Send a report to help fix the problem. You can review the report before sending it.", preferredStyle: .alert) |
| 19 | alert.addAction(UIAlertAction(title: "Send Report", style: .default) { _ in send(report, from: host) }) |
| 20 | alert.addAction(UIAlertAction(title: "Show Report", style: .default) { _ in show(report, from: host) }) |
| 21 | alert.addAction(UIAlertAction(title: "Don’t Send", style: .cancel) { _ in sb_crash_forget() }) |
| 22 | host.present(alert, animated: true) |
| 23 | } |
| 24 | |
| 25 | private static func show(_ report: String, from host: UIViewController) { |
| 26 | let page = UIViewController() |
| 27 | page.title = "Crash Report" |
| 28 | let text = UITextView() |
| 29 | text.text = report |
| 30 | text.font = .monospacedSystemFont(ofSize: 13, weight: .regular) |
| 31 | text.isEditable = false |
| 32 | text.backgroundColor = .systemBackground |
| 33 | page.view = text |
| 34 | let navigation = UINavigationController(rootViewController: page) |
| 35 | page.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Don’t Send", primaryAction: UIAction { [weak navigation] _ in |
| 36 | sb_crash_forget() |
| 37 | navigation?.dismiss(animated: true) |
| 38 | }) |
| 39 | page.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Send Report", primaryAction: UIAction { [weak navigation] _ in |
| 40 | navigation?.dismiss(animated: true) { send(report, from: host) } |
| 41 | }) |
| 42 | host.present(navigation, animated: true) |
| 43 | } |
| 44 | |
| 45 | private static func send(_ report: String, from host: UIViewController) { |
| 46 | let session = URLSession(configuration: .ephemeral) |
| 47 | var request = URLRequest(url: URL(string: String(cString: sb_crash_address()))!) |
| 48 | request.httpMethod = "POST" |
| 49 | request.setValue("text/plain; charset=utf-8", forHTTPHeaderField: "Content-Type") |
| 50 | request.httpBody = Data(report.utf8) |
| 51 | session.dataTask(with: request) { _, response, error in |
| 52 | session.finishTasksAndInvalidate() |
| 53 | let sent = error == nil && (response as? HTTPURLResponse).map { (200..<300).contains($0.statusCode) } == true |
| 54 | DispatchQueue.main.async { |
| 55 | if sent { |
| 56 | sb_crash_forget() |
| 57 | } else { |
| 58 | let alert = UIAlertController(title: "Report wasn’t sent", message: "You can try again.", preferredStyle: .alert) |
| 59 | alert.addAction(UIAlertAction(title: "Try Again", style: .default) { _ in send(report, from: host) }) |
| 60 | alert.addAction(UIAlertAction(title: "Don’t Send", style: .cancel) { _ in sb_crash_forget() }) |
| 61 | host.present(alert, animated: true) |
| 62 | } |
| 63 | } |
| 64 | }.resume() |
| 65 | } |
| 66 | } |