From fb58f26ee039ce1623c48f3f89027702f86c66d7 Mon Sep 17 00:00:00 2001 From: Ethan Wong Date: Sat, 12 Mar 2022 12:43:57 +0800 Subject: [PATCH] Minor updates to subscribe window UI. --- .github/ISSUE_TEMPLATE/bug_report.md | 37 +++ .github/ISSUE_TEMPLATE/crash_report.md | 37 +++ .github/ISSUE_TEMPLATE/feature_request.md | 11 + Doughnut.xcodeproj/project.pbxproj | 34 ++- Doughnut/AppDelegate.swift | 42 ++- Doughnut/Base.lproj/MainMenu.storyboard | 13 + Doughnut/CrashReport/CrashReport.storyboard | 286 ++++++++++++++++++ .../CrashReportViewController.swift | 124 ++++++++ .../CrashReportWindowController.swift | 39 +++ Doughnut/CrashReport/CrashReporter.swift | 84 +++++ Doughnut/Info.plist | 2 + Doughnut/Preference/Preference.swift | 3 + Doughnut/Utilities/ModalSheetSegue.swift | 43 +++ Doughnut/Utilities/NSButton+Extensions.swift | 33 ++ Doughnut/WindowController.swift | 1 + Podfile | 1 + Podfile.lock | 6 +- 17 files changed, 792 insertions(+), 4 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/crash_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 Doughnut/CrashReport/CrashReport.storyboard create mode 100644 Doughnut/CrashReport/CrashReportViewController.swift create mode 100644 Doughnut/CrashReport/CrashReportWindowController.swift create mode 100644 Doughnut/CrashReport/CrashReporter.swift create mode 100644 Doughnut/Utilities/ModalSheetSegue.swift create mode 100644 Doughnut/Utilities/NSButton+Extensions.swift diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..6cadb19 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug Report +about: Report a bug to help us improve Doughnut. +title: "" +labels: "" +assignees: "" +--- + + + +## Steps to Reproduce: + + + +1. ... +2. ... +3. ... + +**Expected results:** + +**Actual results:** + +## Additional Information: + + + +- Doughnut Version: [e.g. 1.1.1 (58)] +- macOS Version: [e.g. 12.3 (21E230)] +- Safari Version: [e.g. 15.4 (17613.1.17.1.6)] + + diff --git a/.github/ISSUE_TEMPLATE/crash_report.md b/.github/ISSUE_TEMPLATE/crash_report.md new file mode 100644 index 0000000..619ff55 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/crash_report.md @@ -0,0 +1,37 @@ +--- +name: Crash Report +about: Report a crash problem. +title: "Crash: " +labels: "" +assignees: "" +--- + + + +## Steps to Reproduce: + + + +1. ... +2. ... +3. ... + +**Crash Report:** + +
+Crash Report + +``` +``` +
+ +## Additional Information: + + + +- Safari Version: [e.g. 15.4 (17613.1.17.1.6)] + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2a4f641 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,11 @@ +--- +name: Feature Request +about: Suggest a new feature for Doughnut. +title: "FR: " +labels: "" +assignees: "" +--- + + diff --git a/Doughnut.xcodeproj/project.pbxproj b/Doughnut.xcodeproj/project.pbxproj index bd7845b..643b02d 100644 --- a/Doughnut.xcodeproj/project.pbxproj +++ b/Doughnut.xcodeproj/project.pbxproj @@ -12,13 +12,19 @@ 5E0288D17E6858855545E02E /* Pods_DoughnutTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BE29F62CFF7B122D5C39998 /* Pods_DoughnutTests.framework */; }; 6B0233BD27B2CA6500500E28 /* ControlMenuProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B0233BC27B2CA6500500E28 /* ControlMenuProvider.swift */; }; 6B0605C52788627D00A8A91E /* NSMenu+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B0605C42788627D00A8A91E /* NSMenu+Extensions.swift */; }; + 6B193AA427A286B800DB1379 /* ModalSheetSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B193AA327A286B800DB1379 /* ModalSheetSegue.swift */; }; 6B36624627CFB339008E1CA5 /* NSImage+Tint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B36624527CFB339008E1CA5 /* NSImage+Tint.swift */; }; 6B3A75F8278F44F500F25578 /* NSView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3A75F7278F44F500F25578 /* NSView+Extensions.swift */; }; + 6B5B39A327A448BF00234D82 /* NSButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5B39A227A448BF00234D82 /* NSButton+Extensions.swift */; }; 6B94DF4C278968F500BCB149 /* NSTableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B94DF4B278968F500BCB149 /* NSTableView+Extensions.swift */; }; 6B96F45D27CE6F10001941BA /* PodcastSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B96F45C27CE6F10001941BA /* PodcastSearchField.swift */; }; 6B9C30BB27B5708300D462BE /* BaseTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9C30BA27B5708300D462BE /* BaseTableView.swift */; }; 6B9E154727C9EA5F00C919D5 /* AppIcon_Big_Sur.icns in Resources */ = {isa = PBXBuildFile; fileRef = 6B9E154627C9EA5F00C919D5 /* AppIcon_Big_Sur.icns */; }; 6BA21C4F279D690700CD3672 /* WindowController+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA21C4E279D690700CD3672 /* WindowController+Toolbar.swift */; }; + 6BA21C57279D74D400CD3672 /* CrashReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA21C56279D74D400CD3672 /* CrashReportViewController.swift */; }; + 6BA21C59279D74DC00CD3672 /* CrashReport.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6BA21C58279D74DC00CD3672 /* CrashReport.storyboard */; }; + 6BA21C5C279D7A1100CD3672 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA21C5B279D7A1100CD3672 /* CrashReporter.swift */; }; + 6BB11F9C27A29B6E009CBAF8 /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB11F9B27A29B6E009CBAF8 /* CrashReportWindowController.swift */; }; 6BB5771E278602B400DFF99F /* MainMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6BB5771C278602B400DFF99F /* MainMenu.storyboard */; }; 6BC6395F27A6800500535897 /* Player+RemoteCommandCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC6395E27A6800500535897 /* Player+RemoteCommandCenter.swift */; }; 6BC6396127A687E400535897 /* Player+NowPlayingInfoCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC6396027A687E400535897 /* Player+NowPlayingInfoCenter.swift */; }; @@ -103,15 +109,21 @@ 5E1DC050EEE121FD033C44DF /* Pods-Doughnut.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Doughnut.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Doughnut/Pods-Doughnut.debug.xcconfig"; sourceTree = ""; }; 6B0233BC27B2CA6500500E28 /* ControlMenuProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlMenuProvider.swift; sourceTree = ""; }; 6B0605C42788627D00A8A91E /* NSMenu+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMenu+Extensions.swift"; sourceTree = ""; }; + 6B193AA327A286B800DB1379 /* ModalSheetSegue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalSheetSegue.swift; sourceTree = ""; }; 6B36624527CFB339008E1CA5 /* NSImage+Tint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSImage+Tint.swift"; sourceTree = ""; }; 6B3A75F7278F44F500F25578 /* NSView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSView+Extensions.swift"; sourceTree = ""; }; 6B3ACC982773555700CF1EF1 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; + 6B5B39A227A448BF00234D82 /* NSButton+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSButton+Extensions.swift"; sourceTree = ""; }; 6B730B732767A90900FB5F84 /* Doughnut-Release.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Doughnut-Release.entitlements"; sourceTree = ""; }; 6B94DF4B278968F500BCB149 /* NSTableView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Extensions.swift"; sourceTree = ""; }; 6B96F45C27CE6F10001941BA /* PodcastSearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodcastSearchField.swift; sourceTree = ""; }; 6B9C30BA27B5708300D462BE /* BaseTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTableView.swift; sourceTree = ""; }; 6B9E154627C9EA5F00C919D5 /* AppIcon_Big_Sur.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon_Big_Sur.icns; sourceTree = ""; }; 6BA21C4E279D690700CD3672 /* WindowController+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WindowController+Toolbar.swift"; sourceTree = ""; }; + 6BA21C56279D74D400CD3672 /* CrashReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportViewController.swift; sourceTree = ""; }; + 6BA21C58279D74DC00CD3672 /* CrashReport.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = CrashReport.storyboard; sourceTree = ""; }; + 6BA21C5B279D7A1100CD3672 /* CrashReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = ""; }; + 6BB11F9B27A29B6E009CBAF8 /* CrashReportWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportWindowController.swift; sourceTree = ""; }; 6BB5771D278602B400DFF99F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainMenu.storyboard; sourceTree = ""; }; 6BC6395E27A6800500535897 /* Player+RemoteCommandCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Player+RemoteCommandCenter.swift"; sourceTree = ""; }; 6BC6396027A687E400535897 /* Player+NowPlayingInfoCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Player+NowPlayingInfoCenter.swift"; sourceTree = ""; }; @@ -221,6 +233,7 @@ 6B0605BF2788624B00A8A91E /* Utilities */ = { isa = PBXGroup; children = ( + 6B193AA327A286B800DB1379 /* ModalSheetSegue.swift */, 6B0605C22788626600A8A91E /* Extensions */, ); path = Utilities; @@ -238,6 +251,7 @@ 6B0605C32788626F00A8A91E /* AppKit */ = { isa = PBXGroup; children = ( + 6B5B39A227A448BF00234D82 /* NSButton+Extensions.swift */, 6B0605C42788627D00A8A91E /* NSMenu+Extensions.swift */, 6B94DF4B278968F500BCB149 /* NSTableView+Extensions.swift */, 6B3A75F7278F44F500F25578 /* NSView+Extensions.swift */, @@ -246,6 +260,17 @@ name = AppKit; sourceTree = ""; }; + 6BB11F9A27A292A3009CBAF8 /* CrashReport */ = { + isa = PBXGroup; + children = ( + 6BA21C5B279D7A1100CD3672 /* CrashReporter.swift */, + 6BB11F9B27A29B6E009CBAF8 /* CrashReportWindowController.swift */, + 6BA21C56279D74D400CD3672 /* CrashReportViewController.swift */, + 6BA21C58279D74DC00CD3672 /* CrashReport.storyboard */, + ); + path = CrashReport; + sourceTree = ""; + }; 6BC6396227A68E0C00535897 /* CoreMedia */ = { isa = PBXGroup; children = ( @@ -370,10 +395,11 @@ 83FB4EB61F7BD996001CD842 /* Library */, 831EBAD427CC0DA500F212B4 /* Player */, 83235BF5200945D900BC356F /* Preference */, - 6B0605BF2788624B00A8A91E /* Utilities */, 830B15991F7B97250086C121 /* Views */, 83667DEE1F76D1F300F1ABC0 /* View Controllers */, 839DBDA51FEEE872007610EF /* Windows */, + 6BB11F9A27A292A3009CBAF8 /* CrashReport */, + 6B0605BF2788624B00A8A91E /* Utilities */, 838257CD1F759F6F00DB4FD1 /* AppDelegate.swift */, 83BA22E21F994EA6006BF58A /* DoughnutApp.swift */, 6B0233BC27B2CA6500500E28 /* ControlMenuProvider.swift */, @@ -606,6 +632,7 @@ files = ( 838257D21F759F6F00DB4FD1 /* Assets.xcassets in Resources */, 6BB5771E278602B400DFF99F /* MainMenu.storyboard in Resources */, + 6BA21C59279D74DC00CD3672 /* CrashReport.storyboard in Resources */, 6BF126D12780727100D840A4 /* EpisodeInfo.storyboard in Resources */, 831EBADA27CC120000F212B4 /* Credits.rtf in Resources */, 6B9E154727C9EA5F00C919D5 /* AppIcon_Big_Sur.icns in Resources */, @@ -783,8 +810,10 @@ buildActionMask = 2147483647; files = ( 8343426A1F802C1400913C0B /* EpisodeFilterViewController.swift in Sources */, + 6B193AA427A286B800DB1379 /* ModalSheetSegue.swift in Sources */, 839A32811FF9831B002D2023 /* ShowEpisodeWindow.swift in Sources */, 8389709C20069D720094795D /* SortingMenuProvider.swift in Sources */, + 6BA21C5C279D7A1100CD3672 /* CrashReporter.swift in Sources */, 83E95A5E1FFBCA3C00C6AABF /* Storage.swift in Sources */, 8341B83B1F7BEB0500B50A5D /* Migrations.swift in Sources */, 6B9C30BB27B5708300D462BE /* BaseTableView.swift in Sources */, @@ -793,17 +822,20 @@ 6BC6395F27A6800500535897 /* Player+RemoteCommandCenter.swift in Sources */, 83D9710C1F812E910091822A /* PlayerView.swift in Sources */, 83235C042009473200BC356F /* PrefLibraryViewController.swift in Sources */, + 6B5B39A327A448BF00234D82 /* NSButton+Extensions.swift in Sources */, 6B0605C52788627D00A8A91E /* NSMenu+Extensions.swift in Sources */, 6BA21C4F279D690700CD3672 /* WindowController+Toolbar.swift in Sources */, 839DBDA41FEEE658007610EF /* ShowPodcastWindow.swift in Sources */, 6B0233BD27B2CA6500500E28 /* ControlMenuProvider.swift in Sources */, 83D9710A1F812B270091822A /* Player.swift in Sources */, + 6BA21C57279D74D400CD3672 /* CrashReportViewController.swift in Sources */, 837D52C01F8E683200C17514 /* DownloadCellView.swift in Sources */, 83DB16E11F93FB460062B266 /* SubscribeViewController.swift in Sources */, 83504DD61FFC377700375BA0 /* EpisodeDownloadTask.swift in Sources */, 83BA22E31F994EA6006BF58A /* DoughnutApp.swift in Sources */, 832A04341F76EBDC00C92D25 /* WindowController.swift in Sources */, 6B94DF4C278968F500BCB149 /* NSTableView+Extensions.swift in Sources */, + 6BB11F9C27A29B6E009CBAF8 /* CrashReportWindowController.swift in Sources */, 830B159B1F7B97910086C121 /* PodcastCellView.swift in Sources */, 83235C072009473500BC356F /* PrefPlaybackViewController.swift in Sources */, 830E99BD1FF50DD000B728BE /* ActivityIndicator.swift in Sources */, diff --git a/Doughnut/AppDelegate.swift b/Doughnut/AppDelegate.swift index 13c9ed3..ddcba44 100644 --- a/Doughnut/AppDelegate.swift +++ b/Doughnut/AppDelegate.swift @@ -22,6 +22,8 @@ import MASPreferences private extension NSUserInterfaceItemIdentifier { + static let doughnutMainMenuDebug = Self("NSDoughnutMainMenuDebug") + static let doughnutViewMenuSortPodcasts = Self("NSDoughnutViewMenuSortPodcastsItem") static let doughnutViewMenuSortEpisodes = Self("NSDoughnutViewMenuSortEpisodesItem") static let doughnutControlMenu = Self("NSDoughnutControlMenuItem") @@ -41,14 +43,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate { ], title: nil) }() + private lazy var crashReportWindowController: CrashReportWindowController? = { + return CrashReportWindowController.instantiateFromMainStoryboard() + }() + override init() { NSWindow.allowsAutomaticWindowTabbing = false } func applicationDidFinishLaunching(_ aNotification: Notification) { // Register NSMenuDelegate for all main menu items - NSApp.mainMenu?.items.forEach { - $0.submenu?.delegate = self + NSApp.mainMenu?.items.forEach { item in + if item.identifier == .doughnutMainMenuDebug { +#if !DEBUG + item.isHidden = !Preference.bool(for: .debugMenuEnabled) +#endif + } + item.submenu?.delegate = self } UserDefaults.standard.register(defaults: Preference.defaultPreference) @@ -61,6 +72,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { createAndShowMainWindow() + showCrashReportWindowIfNeeded() + let connected = Library.global.connect() if !connected { @@ -142,6 +155,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate { assert(false, "This menu item is to be implemented: \(#function)") } + @IBAction func forceCrash(_ sender: AnyObject) { + CrashReporter.shared.forceCrash() + } + +} + +extension AppDelegate { + + @discardableResult + private func showCrashReportWindowIfNeeded() -> Bool { + guard + let crashContent = CrashReporter.shared.getPendingCrashReport(), + let crashReportWindowController = crashReportWindowController, + let crashReportWindow = crashReportWindowController.window + else { + return false + } + + crashReportWindowController.setCrashContent(crashContent) + + NSApp.runModal(for: crashReportWindow) + + return true + } + } extension AppDelegate: NSMenuDelegate { diff --git a/Doughnut/Base.lproj/MainMenu.storyboard b/Doughnut/Base.lproj/MainMenu.storyboard index 7269824..68fbf4f 100644 --- a/Doughnut/Base.lproj/MainMenu.storyboard +++ b/Doughnut/Base.lproj/MainMenu.storyboard @@ -442,6 +442,19 @@ CA + + + + + + + + + + + + + diff --git a/Doughnut/CrashReport/CrashReport.storyboard b/Doughnut/CrashReport/CrashReport.storyboard new file mode 100644 index 0000000..9ad83e8 --- /dev/null +++ b/Doughnut/CrashReport/CrashReport.storyboard @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doughnut/CrashReport/CrashReportViewController.swift b/Doughnut/CrashReport/CrashReportViewController.swift new file mode 100644 index 0000000..64ea2ae --- /dev/null +++ b/Doughnut/CrashReport/CrashReportViewController.swift @@ -0,0 +1,124 @@ +/* + * Doughnut Podcast Client + * Copyright (C) 2017 - 2022 Chris Dyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import AppKit + +final class CrashReportViewController: NSViewController { + + @IBOutlet private weak var contentTextView: NSTextView! + + override func viewDidLoad() { + super.viewDidLoad() + + contentTextView.font = NSFont.userFixedPitchFont(ofSize: 12) + contentTextView.textContainerInset = NSSize(width: 4, height: 4) + } + + override func viewWillAppear() { + super.viewWillAppear() + + view.window?.level = .modalPanel + view.window?.isReleasedWhenClosed = true + view.window?.center() + } + + func setCrashContent(_ content: String) { + contentTextView.string = content + } + + @IBAction private func sendCrashLog(_ sender: Any) { + if let crashReportURLStr = Bundle.main.infoDictionary?["DoughnutCrashReportURL"] as? String, + let crashReportURL = URL(string: crashReportURLStr) + { + NSWorkspace.shared.open(crashReportURL) + } + } + + @IBAction private func dismissCrashReport(_ sender: Any) { + view.window?.windowController?.close() + } + +} + +final class TroubleshootingViewController: NSViewController { + + @IBOutlet weak var resetPreferencesButton: NSButton! + @IBOutlet weak var relocateLibraryButton: NSButton! + + override func viewDidLoad() { + super.viewDidLoad() + resetPreferencesButton.setTitleColor(NSColor.systemRed) + } + + @IBAction private func troubleshootingResetPreferences(_ sender: Any) { + let alert = NSAlert() + alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: "Confirm") + alert.messageText = "Are you sure you want to restore all preferences to their default settings?" + alert.informativeText = "You can’t undo this action." + + guard alert.runModal() == .alertSecondButtonReturn else { + return + } + + dismiss(self) + + UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) + UserDefaults.standard.synchronize() + + promptRestart() + } + + @IBAction private func troubleshootingRelocateLibrary(_ sender: Any) { + let panel = NSOpenPanel() + panel.canChooseDirectories = true + panel.canChooseFiles = false + panel.allowsMultipleSelection = false + + guard panel.runModal() == .OK, let url = panel.url else { + return + } + + dismiss(self) + + Preference.set(url, for: Preference.Key.libraryPath) + + promptRestart() + } + + private func promptRestart() { + let alert = NSAlert() + alert.addButton(withTitle: "OK") + alert.messageText = "Doughnut Will Restart" + alert.informativeText = "Doughnut will restart to apply these changes." + + alert.runModal() + + let task = Process() + + var args = [String]() + args.append("-c") + args.append("sleep 0.2; open \"\(Bundle.main.bundlePath)\"") + + task.launchPath = "/bin/sh" + task.arguments = args + task.launch() + NSApp.terminate(self) + } + +} diff --git a/Doughnut/CrashReport/CrashReportWindowController.swift b/Doughnut/CrashReport/CrashReportWindowController.swift new file mode 100644 index 0000000..de6d72e --- /dev/null +++ b/Doughnut/CrashReport/CrashReportWindowController.swift @@ -0,0 +1,39 @@ +/* + * Doughnut Podcast Client + * Copyright (C) 2017 - 2022 Chris Dyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import AppKit + +final class CrashReportWindowController: NSWindowController, NSWindowDelegate { + + private var crashReportViewController: CrashReportViewController { + return contentViewController as! CrashReportViewController + } + + static func instantiateFromMainStoryboard() -> Self? { + return NSStoryboard(name: "CrashReport", bundle: nil).instantiateInitialController() as? Self + } + + func setCrashContent(_ content: String) { + crashReportViewController.setCrashContent(content) + } + + func windowWillClose(_ notification: Notification) { + NSApp.stopModal(withCode: .OK) + } + +} diff --git a/Doughnut/CrashReport/CrashReporter.swift b/Doughnut/CrashReport/CrashReporter.swift new file mode 100644 index 0000000..23336d2 --- /dev/null +++ b/Doughnut/CrashReport/CrashReporter.swift @@ -0,0 +1,84 @@ +/* + * Doughnut Podcast Client + * Copyright (C) 2017 - 2022 Chris Dyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Foundation + +import CrashReporter + +final class CrashReporter { + + static var shared: CrashReporter { + return sharedInstance + } + + private static var sharedInstance = CrashReporter() + + private var plCrashReporter: PLCrashReporter! + + private init() { + setup() + } + + private func setup() { + let config = PLCrashReporterConfig.defaultConfiguration() + guard let plCrashReporter = PLCrashReporter(configuration: config) else { + print("CrashReporter: could not create an instance of PLCrashReporter") + return + } + self.plCrashReporter = plCrashReporter + // enable the PLCrashReporter + do { + try plCrashReporter.enableAndReturnError() + } catch { + print("CrashReporter: failed to enable PLCrashReporter: \(error)") + } + } + + func getPendingCrashReport() -> String? { + guard plCrashReporter.hasPendingCrashReport() else { + return nil + } + + defer { + // purge the report + plCrashReporter.purgePendingCrashReport() + } + + do { + let data = try plCrashReporter.loadPendingCrashReportDataAndReturnError() + + // retrieve crash reporter data + let report = try PLCrashReport(data: data) + + if let text = PLCrashReportTextFormatter.stringValue(for: report, with: PLCrashReportTextFormatiOS) { + return text + } else { + print("CrashReporter: can't convert the report to text") + } + } catch { + print("CrashReporter failed to load and parse crash report: \(error)") + } + + return nil + } + + func forceCrash() { + fatalError("Force crashd in \(#function)") + } + +} diff --git a/Doughnut/Info.plist b/Doughnut/Info.plist index 3751b8b..739d4d0 100644 --- a/Doughnut/Info.plist +++ b/Doughnut/Info.plist @@ -20,6 +20,8 @@ 2.0.0.beta1 CFBundleVersion 84 + DoughnutCrashReportURL + https://github.com/dyerc/Doughnut/issues/new?template=crash_report.md LSApplicationCategoryType public.app-category.news LSMinimumSystemVersion diff --git a/Doughnut/Preference/Preference.swift b/Doughnut/Preference/Preference.swift index bfbf690..ac8956f 100644 --- a/Doughnut/Preference/Preference.swift +++ b/Doughnut/Preference/Preference.swift @@ -50,6 +50,9 @@ class Preference { static let skipForwardDuration = Key("skipForwardDuration") static let skipBackDuration = Key("skipBackDuration") static let replayAfterPause = Key("replayAfterPause") + + // Debuging + static let debugMenuEnabled = Key("debugMenuEnabled") } enum AppIconStyle: Int { diff --git a/Doughnut/Utilities/ModalSheetSegue.swift b/Doughnut/Utilities/ModalSheetSegue.swift new file mode 100644 index 0000000..d1bc7c4 --- /dev/null +++ b/Doughnut/Utilities/ModalSheetSegue.swift @@ -0,0 +1,43 @@ +/* + * Doughnut Podcast Client + * Copyright (C) 2017 - 2022 Chris Dyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import AppKit + +final class ModalSheetStoryboardSegue: NSStoryboardSegue { + + override func perform() { + let resolveViewController: (Any) -> NSViewController? = { controller in + if let controller = controller as? NSViewController { + return controller + } else if let controller = controller as? NSWindowController { + return controller.contentViewController + } + return nil + } + + guard + let sourceViewController = resolveViewController(sourceController), + let destinationViewController = resolveViewController(destinationController) + else { + assert(false, "ModalSheetStoryboardSegue: failed to resolve viewControllers in \(#function)") + return + } + sourceViewController.presentAsSheet(destinationViewController) + } + +} diff --git a/Doughnut/Utilities/NSButton+Extensions.swift b/Doughnut/Utilities/NSButton+Extensions.swift new file mode 100644 index 0000000..615d25b --- /dev/null +++ b/Doughnut/Utilities/NSButton+Extensions.swift @@ -0,0 +1,33 @@ +/* + * Doughnut Podcast Client + * Copyright (C) 2017 - 2022 Chris Dyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import AppKit + +extension NSButton { + + func setTitleColor(_ color: NSColor) { + let coloredTitle = NSMutableAttributedString( + string: title, + attributes: [ + .foregroundColor: color, + ] + ) + attributedTitle = coloredTitle + } + +} diff --git a/Doughnut/WindowController.swift b/Doughnut/WindowController.swift index 658dd21..578860e 100644 --- a/Doughnut/WindowController.swift +++ b/Doughnut/WindowController.swift @@ -38,6 +38,7 @@ final class WindowController: NSWindowController, NSWindowDelegate, NSTextFieldD super.windowDidLoad() window?.titleVisibility = .hidden + window?.center() if #available(macOS 11.0, *) { window?.toolbarStyle = .unified diff --git a/Podfile b/Podfile index 93a6110..323f41f 100644 --- a/Podfile +++ b/Podfile @@ -10,6 +10,7 @@ target 'Doughnut' do pod 'FeedKit', '9.1.2' pod 'MASPreferences', :git => 'https://github.com/shpakovski/MASPreferences.git', :commit => '135869c' pod 'Sparkle', '1.27.1' + pod 'PLCrashReporter', '1.10.1' end target 'DoughnutTests' do diff --git a/Podfile.lock b/Podfile.lock index ee3e998..8a374da 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -4,18 +4,21 @@ PODS: - GRDB.swift/standard (= 5.17.0) - GRDB.swift/standard (5.17.0) - MASPreferences (1.3) + - PLCrashReporter (1.10.1) - Sparkle (1.27.1) DEPENDENCIES: - FeedKit (= 9.1.2) - GRDB.swift (= 5.17.0) - MASPreferences (from `https://github.com/shpakovski/MASPreferences.git`, commit `135869c`) + - PLCrashReporter (= 1.10.1) - Sparkle (= 1.27.1) SPEC REPOS: trunk: - FeedKit - GRDB.swift + - PLCrashReporter - Sparkle EXTERNAL SOURCES: @@ -32,8 +35,9 @@ SPEC CHECKSUMS: FeedKit: 71653273ab08e618cd6fd1301ca08fc02dca6a9e GRDB.swift: 1c8a479b2723beab39ed8609fe25513483a0f282 MASPreferences: f5cf4c3f91714fcb4e1ce21b7002880353fe7c5e + PLCrashReporter: b30195e509f07299ea277d1997b3a39449d05698 Sparkle: 23f98b268284c8c03e6228230fc8f1807ef041d5 -PODFILE CHECKSUM: 0b464282dec6fd43ebd277f4b7c863210e357597 +PODFILE CHECKSUM: eda8b35c82199a93c745135e96097d7a487d5530 COCOAPODS: 1.11.2