From 1f95f1e9cc2ca33fe2e4cd7739b9af8e3fa49150 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Thu, 30 Jun 2022 13:39:03 +0100 Subject: [PATCH] Replace the settings page with a preferences window (#46) * Replaces the settings page with a preferences window * Removes unused icons * Ensures bundler is installed on CI * Updates fastlane * Add a tests for migrating SettingsV0 to Settings --- .circleci/config.yml | 1 + Gemfile.lock | 89 +-- SeaEye.xcodeproj/project.pbxproj | 102 +++- SeaEye/AppDelegate.swift | 4 +- SeaEye/circleci-normal-alt.png | Bin 3075 -> 0 bytes SeaEye/circleci-normal.png | Bin 3046 -> 0 bytes .../controllers/SeaEyeBuildsController.swift | 5 +- SeaEye/controllers/SeaEyeIconController.swift | 12 +- .../controllers/SeaEyePopoverController.swift | 17 +- .../SeaEyeSettingsController.swift | 112 ---- .../CurrentProjectsController.swift | 188 ++++++ .../preferences/PreferencesWindow.swift | 149 +++++ .../PreferencesWindowController.swift | 32 + .../controllers/preferences/ServerTable.swift | 85 +++ .../UnfollowedProjectsController.swift | 79 +++ SeaEye/listeners/NotificationListener.swift | 22 +- SeaEye/models/ClientProject.swift | 6 + SeaEye/models/Project.swift | 3 +- SeaEye/models/Settings.swift | 97 ++- SeaEye/models/SettingsV0.swift | 90 +++ SeaEye/networking/CircleCIClient.swift | 26 + SeaEye/views/FallbackView.swift | 19 +- SeaEye/views/PreferencesWindow.xib | 569 ++++++++++++++++++ SeaEye/views/SeaEyeSettingsController.xib | 182 ------ SeaEye/views/SelectedServerView.swift | 14 + SeaEyeTests/{models => }/FilterTest.swift | 2 +- SeaEyeTests/NewBuildFilterTest.swift | 57 ++ SeaEyeTests/NotificationListenerTest.swift | 3 +- SeaEyeTests/SettingsTest.swift | 55 +- ...overContollerBuildUpdateListenerTest.swift | 3 +- .../SeaEyeStatusBarListenerTest.swift | 14 +- SeaEyeTests/models/ProjectTest.swift | 15 + 32 files changed, 1601 insertions(+), 451 deletions(-) delete mode 100644 SeaEye/circleci-normal-alt.png delete mode 100644 SeaEye/circleci-normal.png delete mode 100644 SeaEye/controllers/SeaEyeSettingsController.swift create mode 100644 SeaEye/controllers/preferences/CurrentProjectsController.swift create mode 100644 SeaEye/controllers/preferences/PreferencesWindow.swift create mode 100644 SeaEye/controllers/preferences/PreferencesWindowController.swift create mode 100644 SeaEye/controllers/preferences/ServerTable.swift create mode 100644 SeaEye/controllers/preferences/UnfollowedProjectsController.swift create mode 100644 SeaEye/models/ClientProject.swift create mode 100644 SeaEye/models/SettingsV0.swift create mode 100644 SeaEye/views/PreferencesWindow.xib delete mode 100644 SeaEye/views/SeaEyeSettingsController.xib create mode 100644 SeaEye/views/SelectedServerView.swift rename SeaEyeTests/{models => }/FilterTest.swift (99%) create mode 100644 SeaEyeTests/NewBuildFilterTest.swift create mode 100644 SeaEyeTests/models/ProjectTest.swift diff --git a/.circleci/config.yml b/.circleci/config.yml index edc9963..f2bac7c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,6 +11,7 @@ jobs: shell: /bin/bash --login -eo pipefail steps: - checkout + - run: gem install bundler -v 2.0.1 - run: bundle install - run: bundle exec fastlane gym - run: bundle exec fastlane test diff --git a/Gemfile.lock b/Gemfile.lock index ee8134f..646f62d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,9 +2,9 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.0) - addressable (2.5.2) + addressable (2.6.0) public_suffix (>= 2.0.2, < 4.0) - atomos (0.1.2) + atomos (0.1.3) babosa (1.0.2) claide (1.0.2) colored (1.2) @@ -13,35 +13,37 @@ GEM highline (~> 1.7.2) declarative (0.0.10) declarative-option (0.1.0) + digest-crc (0.4.1) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - dotenv (2.4.0) - emoji_regex (0.1.1) + dotenv (2.6.0) + emoji_regex (1.0.1) excon (0.62.0) - faraday (0.15.2) + faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) faraday (>= 0.7.4) http-cookie (~> 1.0.0) - faraday_middleware (0.12.2) + faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) - fastimage (2.1.3) - fastlane (2.97.0) + fastimage (2.1.5) + fastlane (2.116.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) colored commander-fastlane (>= 4.4.6, < 5.0.0) dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (~> 0.1) + emoji_regex (>= 0.1, < 2.0) excon (>= 0.45.0, < 1.0.0) faraday (~> 0.9) faraday-cookie_jar (~> 0.0.6) faraday_middleware (~> 0.9) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.21.2, < 0.22.0) + google-api-client (>= 0.21.2, < 0.24.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) mini_magick (~> 4.5.1) @@ -50,7 +52,7 @@ GEM multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) public_suffix (~> 2.0.0) - rubyzip (>= 1.2.1, < 2.0.0) + rubyzip (>= 1.2.2, < 2.0.0) security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) @@ -59,24 +61,33 @@ GEM tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.5.7, < 2.0.0) - xcpretty (>= 0.2.4, < 1.0.0) + xcodeproj (>= 1.6.0, < 2.0.0) + xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) gh_inspector (1.1.3) - google-api-client (0.21.2) + google-api-client (0.23.9) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.5, < 0.7.0) httpclient (>= 2.8.1, < 3.0) mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - googleauth (0.6.2) + signet (~> 0.9) + google-cloud-core (1.3.0) + google-cloud-env (~> 1.0) + google-cloud-env (1.0.5) + faraday (~> 0.11) + google-cloud-storage (1.16.0) + digest-crc (~> 0.4) + google-api-client (~> 0.23) + google-cloud-core (~> 1.2) + googleauth (>= 0.6.2, < 0.10.0) + googleauth (0.6.7) faraday (~> 0.12) jwt (>= 1.4, < 3.0) - logging (~> 2.0) - memoist (~> 0.12) + memoist (~> 0.16) multi_json (~> 1.11) - os (~> 0.9) + os (>= 0.9, < 2.0) signet (~> 0.7) highline (1.7.10) http-cookie (1.0.3) @@ -84,22 +95,18 @@ GEM httpclient (2.8.3) json (2.1.0) jwt (2.1.0) - little-plugger (1.1.4) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) memoist (0.16.0) - mime-types (3.1) + mime-types (3.2.2) mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) + mime-types-data (3.2018.0812) mini_magick (4.5.1) multi_json (1.13.1) multi_xml (0.6.0) multipart-post (2.0.0) - nanaimo (0.2.5) + nanaimo (0.2.6) naturally (2.2.0) - os (0.9.6) - plist (3.4.0) + os (1.0.0) + plist (3.5.0) public_suffix (2.0.5) representable (3.0.4) declarative (< 0.1.0) @@ -107,37 +114,37 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - rubyzip (1.2.1) + rubyzip (1.2.2) security (0.1.3) - signet (0.8.1) + signet (0.11.0) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.4) + simctl (1.6.5) CFPropertyList naturally slack-notifier (2.3.2) terminal-notifier (1.8.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - tty-cursor (0.5.0) - tty-screen (0.6.4) - tty-spinner (0.8.0) - tty-cursor (>= 0.5.0) + tty-cursor (0.6.0) + tty-screen (0.6.5) + tty-spinner (0.9.0) + tty-cursor (~> 0.6.0) uber (0.1.0) unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.4.0) + unicode-display_width (1.4.1) word_wrap (1.0.0) - xcodeproj (1.5.9) + xcodeproj (1.8.0) CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.2) + atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.5) - xcpretty (0.2.8) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.0) xcpretty (~> 0.2, >= 0.0.7) @@ -149,4 +156,4 @@ DEPENDENCIES fastlane BUNDLED WITH - 1.15.4 + 2.0.1 diff --git a/SeaEye.xcodeproj/project.pbxproj b/SeaEye.xcodeproj/project.pbxproj index b9dae63..2e5d6c1 100644 --- a/SeaEye.xcodeproj/project.pbxproj +++ b/SeaEye.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 5394798D1FCB909600D6FA19 /* CircleCIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539479741FCB639300D6FA19 /* CircleCIClient.swift */; }; 53A3BE8D1FD34D85006961C6 /* circleci2-project.json in Resources */ = {isa = PBXBuildFile; fileRef = 53A3BE8C1FD34D85006961C6 /* circleci2-project.json */; }; 6008690E1A1D3E5E00600E4C /* SeaEyeBuildsController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6008690D1A1D3E5E00600E4C /* SeaEyeBuildsController.xib */; }; - 600869101A1D3E6F00600E4C /* SeaEyeSettingsController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6008690F1A1D3E6F00600E4C /* SeaEyeSettingsController.xib */; }; 600869121A1D3E9200600E4C /* SeaEyePopoverController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 600869111A1D3E9200600E4C /* SeaEyePopoverController.xib */; }; 600869141A1D3EA000600E4C /* SeaEyeUpdatesController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 600869131A1D3EA000600E4C /* SeaEyeUpdatesController.xib */; }; 6008691D1A1D4F7400600E4C /* Main.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6008691C1A1D4F7400600E4C /* Main.xib */; }; @@ -41,7 +40,6 @@ 60C73C0619FC16FE0067CDCA /* SeaEyePopoverController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C0519FC16FE0067CDCA /* SeaEyePopoverController.swift */; }; 60C73C0919FC23F60067CDCA /* SeaEyeIconController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C0719FC23F60067CDCA /* SeaEyeIconController.swift */; }; 60C73C1519FD7ECB0067CDCA /* SeaEyeBuildsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */; }; - 60C73C1819FD7EE10067CDCA /* SeaEyeSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */; }; 60F0232C1A1A95930067C0A0 /* SeaEyeUpdatesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */; }; F412CA9221D3DF050069C7C7 /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F412CA9121D3DF050069C7C7 /* NSImage.swift */; }; F412CA9721D3E3530069C7C7 /* HTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F412CA9621D3E3530069C7C7 /* HTTPRequest.swift */; }; @@ -55,9 +53,17 @@ F422C54920D48CDD002A5897 /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F422C54720D48CC2002A5897 /* VersionNumber.swift */; }; F422C54B20D48CF2002A5897 /* VersionNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */; }; F4472C0C216D72B700ECD077 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4472C0B216D72B700ECD077 /* Main.storyboard */; }; + F47EA511225410480008FE25 /* ProjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47EA510225410480008FE25 /* ProjectTest.swift */; }; + F47EA5132254119B0008FE25 /* NewBuildFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47EA5122254119B0008FE25 /* NewBuildFilterTest.swift */; }; + F4917465221C4B5300CAF9C1 /* SelectedServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */; }; + F4917466221C4CA600CAF9C1 /* SelectedServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */; }; + F4917468221C4E0F00CAF9C1 /* ClientProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917467221C4E0F00CAF9C1 /* ClientProject.swift */; }; + F4917469221C4E0F00CAF9C1 /* ClientProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917467221C4E0F00CAF9C1 /* ClientProject.swift */; }; + F491746B221C4E4000CAF9C1 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F491746A221C4E4000CAF9C1 /* Settings.swift */; }; + F491746C221C4E4000CAF9C1 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F491746A221C4E4000CAF9C1 /* Settings.swift */; }; F4B672D821B60A7C003E30A9 /* ApplicationStartupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B672D721B60A7C003E30A9 /* ApplicationStartupManager.swift */; }; - F4D5A43221E3F6B3006140DE /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* Settings.swift */; }; - F4D5A43321E3F6B3006140DE /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* Settings.swift */; }; + F4D5A43221E3F6B3006140DE /* SettingsV0.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* SettingsV0.swift */; }; + F4D5A43321E3F6B3006140DE /* SettingsV0.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* SettingsV0.swift */; }; F4D5A43521E3F782006140DE /* FallbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43421E3F782006140DE /* FallbackView.swift */; }; F4D5A43621E3F782006140DE /* FallbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43421E3F782006140DE /* FallbackView.swift */; }; F4D5A43821E3FE32006140DE /* BuildSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43721E3FE32006140DE /* BuildSummary.swift */; }; @@ -67,7 +73,6 @@ F4D5A43E21E41FEB006140DE /* SeaEyeStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43C21E41FEB006140DE /* SeaEyeStatusBar.swift */; }; F4D5A43F21E4201F006140DE /* SeaEyeIconController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C0719FC23F60067CDCA /* SeaEyeIconController.swift */; }; F4D5A44021E42057006140DE /* SeaEyePopoverController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C0519FC16FE0067CDCA /* SeaEyePopoverController.swift */; }; - F4D5A44121E4206D006140DE /* SeaEyeSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */; }; F4D5A44221E42076006140DE /* SeaEyeBuildsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */; }; F4D5A44321E4207C006140DE /* SeaEyeUpdatesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */; }; F4D5A44421E4208A006140DE /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F412CA9121D3DF050069C7C7 /* NSImage.swift */; }; @@ -96,6 +101,17 @@ F4F4807221E46CE90038DC09 /* NotificationListenerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807121E46CE90038DC09 /* NotificationListenerTest.swift */; }; F4F4807421E471DE0038DC09 /* SettingsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807321E471DE0038DC09 /* SettingsTest.swift */; }; F4F4807621E4DD6A0038DC09 /* PopoverContollerBuildUpdateListenerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807521E4DD6A0038DC09 /* PopoverContollerBuildUpdateListenerTest.swift */; }; + F4F4807821E518CE0038DC09 /* PreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = F4F4807721E518CE0038DC09 /* PreferencesWindow.xib */; }; + F4F4807A21E518F20038DC09 /* PreferencesWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807921E518F20038DC09 /* PreferencesWindow.swift */; }; + F4F4807C21E5191D0038DC09 /* CurrentProjectsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807B21E5191D0038DC09 /* CurrentProjectsController.swift */; }; + F4F4807E21E519350038DC09 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807D21E519350038DC09 /* PreferencesWindowController.swift */; }; + F4F4808021E5194A0038DC09 /* ServerTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807F21E5194A0038DC09 /* ServerTable.swift */; }; + F4F4808221E5196B0038DC09 /* UnfollowedProjectsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4808121E5196B0038DC09 /* UnfollowedProjectsController.swift */; }; + F4F4808321E51B1B0038DC09 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807D21E519350038DC09 /* PreferencesWindowController.swift */; }; + F4F4808421E51B2C0038DC09 /* PreferencesWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807921E518F20038DC09 /* PreferencesWindow.swift */; }; + F4F4808621E51B4D0038DC09 /* CurrentProjectsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807B21E5191D0038DC09 /* CurrentProjectsController.swift */; }; + F4F4808721E51B510038DC09 /* ServerTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807F21E5194A0038DC09 /* ServerTable.swift */; }; + F4F4808821E51B530038DC09 /* UnfollowedProjectsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4808121E5196B0038DC09 /* UnfollowedProjectsController.swift */; }; F4FFD80421CA897F00E8C82E /* circleci-workflow.json in Resources */ = {isa = PBXBuildFile; fileRef = F4FFD80321CA897F00E8C82E /* circleci-workflow.json */; }; /* End PBXBuildFile section */ @@ -115,7 +131,6 @@ 5394797C1FCB8C5200D6FA19 /* CircleCIBuild.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleCIBuild.swift; sourceTree = ""; }; 53A3BE8C1FD34D85006961C6 /* circleci2-project.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "circleci2-project.json"; sourceTree = ""; }; 6008690D1A1D3E5E00600E4C /* SeaEyeBuildsController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeaEyeBuildsController.xib; sourceTree = ""; }; - 6008690F1A1D3E6F00600E4C /* SeaEyeSettingsController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeaEyeSettingsController.xib; sourceTree = ""; }; 600869111A1D3E9200600E4C /* SeaEyePopoverController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeaEyePopoverController.xib; sourceTree = ""; }; 600869131A1D3EA000600E4C /* SeaEyeUpdatesController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeaEyeUpdatesController.xib; sourceTree = ""; }; 6008691C1A1D4F7400600E4C /* Main.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Main.xib; sourceTree = ""; }; @@ -144,7 +159,6 @@ 60C73C0519FC16FE0067CDCA /* SeaEyePopoverController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyePopoverController.swift; sourceTree = ""; }; 60C73C0719FC23F60067CDCA /* SeaEyeIconController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyeIconController.swift; sourceTree = ""; }; 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyeBuildsController.swift; sourceTree = ""; }; - 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyeSettingsController.swift; sourceTree = ""; }; 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyeUpdatesController.swift; sourceTree = ""; }; F412CA9121D3DF050069C7C7 /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = ""; }; F412CA9621D3E3530069C7C7 /* HTTPRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPRequest.swift; sourceTree = ""; }; @@ -154,14 +168,19 @@ F422C54720D48CC2002A5897 /* VersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumber.swift; sourceTree = ""; }; F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumberTests.swift; sourceTree = ""; }; F4472C0B216D72B700ECD077 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + F47EA510225410480008FE25 /* ProjectTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ProjectTest.swift; path = models/ProjectTest.swift; sourceTree = ""; }; + F47EA5122254119B0008FE25 /* NewBuildFilterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewBuildFilterTest.swift; sourceTree = ""; }; + F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedServerView.swift; sourceTree = ""; }; + F4917467221C4E0F00CAF9C1 /* ClientProject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProject.swift; sourceTree = ""; }; + F491746A221C4E4000CAF9C1 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; F4B672D721B60A7C003E30A9 /* ApplicationStartupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStartupManager.swift; sourceTree = ""; }; - F4D5A43121E3F6B3006140DE /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + F4D5A43121E3F6B3006140DE /* SettingsV0.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsV0.swift; sourceTree = ""; }; F4D5A43421E3F782006140DE /* FallbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackView.swift; sourceTree = ""; }; F4D5A43721E3FE32006140DE /* BuildSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildSummary.swift; sourceTree = ""; }; F4D5A43A21E40E4C006140DE /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; F4D5A43C21E41FEB006140DE /* SeaEyeStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeaEyeStatusBar.swift; sourceTree = ""; }; F4D5A44921E427BB006140DE /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = ""; }; - F4D5A44C21E42A77006140DE /* FilterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FilterTest.swift; path = models/FilterTest.swift; sourceTree = ""; }; + F4D5A44C21E42A77006140DE /* FilterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTest.swift; sourceTree = ""; }; F4D5A44F21E44BA6006140DE /* ClientBuildUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientBuildUpdater.swift; sourceTree = ""; }; F4D5A45221E44BF1006140DE /* NewBuildFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewBuildFilter.swift; sourceTree = ""; }; F4D5A45621E451BA006140DE /* TextPrinter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextPrinter.swift; sourceTree = ""; }; @@ -173,6 +192,12 @@ F4F4807121E46CE90038DC09 /* NotificationListenerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationListenerTest.swift; sourceTree = ""; }; F4F4807321E471DE0038DC09 /* SettingsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTest.swift; sourceTree = ""; }; F4F4807521E4DD6A0038DC09 /* PopoverContollerBuildUpdateListenerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PopoverContollerBuildUpdateListenerTest.swift; path = listeners/PopoverContollerBuildUpdateListenerTest.swift; sourceTree = ""; }; + F4F4807721E518CE0038DC09 /* PreferencesWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesWindow.xib; sourceTree = ""; }; + F4F4807921E518F20038DC09 /* PreferencesWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindow.swift; sourceTree = ""; }; + F4F4807B21E5191D0038DC09 /* CurrentProjectsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentProjectsController.swift; sourceTree = ""; }; + F4F4807D21E519350038DC09 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; + F4F4807F21E5194A0038DC09 /* ServerTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTable.swift; sourceTree = ""; }; + F4F4808121E5196B0038DC09 /* UnfollowedProjectsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfollowedProjectsController.swift; sourceTree = ""; }; F4FFD80321CA897F00E8C82E /* circleci-workflow.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "circleci-workflow.json"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -213,10 +238,11 @@ 6014AD8D19FED4C400865329 /* BuildView.swift */, F4D5A43421E3F782006140DE /* FallbackView.swift */, F4D5A43C21E41FEB006140DE /* SeaEyeStatusBar.swift */, + F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */, 600869131A1D3EA000600E4C /* SeaEyeUpdatesController.xib */, 6008690D1A1D3E5E00600E4C /* SeaEyeBuildsController.xib */, - 6008690F1A1D3E6F00600E4C /* SeaEyeSettingsController.xib */, 600869111A1D3E9200600E4C /* SeaEyePopoverController.xib */, + F4F4807721E518CE0038DC09 /* PreferencesWindow.xib */, ); path = views; sourceTree = ""; @@ -238,11 +264,13 @@ 603314A419FDCBD90073EAED /* models */ = { isa = PBXGroup; children = ( + F4917467221C4E0F00CAF9C1 /* ClientProject.swift */, 5394797C1FCB8C5200D6FA19 /* CircleCIBuild.swift */, F4D5A44921E427BB006140DE /* Filter.swift */, 6014AD981A09874D00865329 /* Project.swift */, 607C78251A190C580040FD5C /* SeaEyeStatus.swift */, - F4D5A43121E3F6B3006140DE /* Settings.swift */, + F4D5A43121E3F6B3006140DE /* SettingsV0.swift */, + F491746A221C4E4000CAF9C1 /* Settings.swift */, ); path = models; sourceTree = ""; @@ -312,7 +340,8 @@ 60C73BEF19FC025A0067CDCA /* Supporting Files */, 5389EF491FCBBA49001C0D5F /* DecodingTests.swift */, F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */, - F4D5A44C21E42A77006140DE /* FilterTest.swift */, + F47EA5122254119B0008FE25 /* NewBuildFilterTest.swift */, + F47EA50F225410330008FE25 /* models */, F4F4807321E471DE0038DC09 /* SettingsTest.swift */, ); path = SeaEyeTests; @@ -345,8 +374,8 @@ 60C73C0719FC23F60067CDCA /* SeaEyeIconController.swift */, 60C73C0519FC16FE0067CDCA /* SeaEyePopoverController.swift */, 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */, - 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */, 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */, + F4F4808521E51B390038DC09 /* preferences */, ); path = controllers; sourceTree = ""; @@ -371,6 +400,15 @@ path = networking; sourceTree = ""; }; + F47EA50F225410330008FE25 /* models */ = { + isa = PBXGroup; + children = ( + F4D5A44C21E42A77006140DE /* FilterTest.swift */, + F47EA510225410480008FE25 /* ProjectTest.swift */, + ); + name = models; + sourceTree = ""; + }; F4D5A45521E45196006140DE /* listeners */ = { isa = PBXGroup; children = ( @@ -392,6 +430,18 @@ name = listeners; sourceTree = ""; }; + F4F4808521E51B390038DC09 /* preferences */ = { + isa = PBXGroup; + children = ( + F4F4807921E518F20038DC09 /* PreferencesWindow.swift */, + F4F4807B21E5191D0038DC09 /* CurrentProjectsController.swift */, + F4F4807D21E519350038DC09 /* PreferencesWindowController.swift */, + F4F4807F21E5194A0038DC09 /* ServerTable.swift */, + F4F4808121E5196B0038DC09 /* UnfollowedProjectsController.swift */, + ); + path = preferences; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -479,8 +529,8 @@ 600869141A1D3EA000600E4C /* SeaEyeUpdatesController.xib in Resources */, 601D48CE1A0A841F007EA297 /* icon_32x32@2x.png in Resources */, 601D48CC1A0A841F007EA297 /* icon_256x256.png in Resources */, - 600869101A1D3E6F00600E4C /* SeaEyeSettingsController.xib in Resources */, 607C77F21A1860070040FD5C /* build-failed@2x.png in Resources */, + F4F4807821E518CE0038DC09 /* PreferencesWindow.xib in Resources */, 602BB1C11A1A93E900E50B78 /* update@2x.png in Resources */, 601D48CF1A0A841F007EA297 /* icon_512x512.png in Resources */, 601D48CD1A0A841F007EA297 /* icon_32x32.png in Resources */, @@ -527,20 +577,27 @@ F4D5A43D21E41FEB006140DE /* SeaEyeStatusBar.swift in Sources */, 60C73C1519FD7ECB0067CDCA /* SeaEyeBuildsController.swift in Sources */, F4D5A45721E451BA006140DE /* TextPrinter.swift in Sources */, + F4917465221C4B5300CAF9C1 /* SelectedServerView.swift in Sources */, + F4F4808221E5196B0038DC09 /* UnfollowedProjectsController.swift in Sources */, + F4F4807E21E519350038DC09 /* PreferencesWindowController.swift in Sources */, 60C73C0619FC16FE0067CDCA /* SeaEyePopoverController.swift in Sources */, - 60C73C1819FD7EE10067CDCA /* SeaEyeSettingsController.swift in Sources */, + F491746B221C4E4000CAF9C1 /* Settings.swift in Sources */, F4D5A43B21E40E4C006140DE /* Notifications.swift in Sources */, F412CA9D21D3ED0D0069C7C7 /* BuildDecorator.swift in Sources */, F412CA9F21D3EFEF0069C7C7 /* DateFormatter.swift in Sources */, 5394797D1FCB8C5200D6FA19 /* CircleCIBuild.swift in Sources */, + F4917468221C4E0F00CAF9C1 /* ClientProject.swift in Sources */, F4D5A45D21E454F4006140DE /* NotificationListener.swift in Sources */, 60F0232C1A1A95930067C0A0 /* SeaEyeUpdatesController.swift in Sources */, F4D5A43821E3FE32006140DE /* BuildSummary.swift in Sources */, F4D5A45A21E45257006140DE /* SeaEyeStatusBarListener.swift in Sources */, F412CA9721D3E3530069C7C7 /* HTTPRequest.swift in Sources */, F4D5A46021E45613006140DE /* PopoverContollerBuildUpdateListener.swift in Sources */, - F4D5A43221E3F6B3006140DE /* Settings.swift in Sources */, + F4D5A43221E3F6B3006140DE /* SettingsV0.swift in Sources */, + F4F4808021E5194A0038DC09 /* ServerTable.swift in Sources */, F4D5A45021E44BA6006140DE /* ClientBuildUpdater.swift in Sources */, + F4F4807C21E5191D0038DC09 /* CurrentProjectsController.swift in Sources */, + F4F4807A21E518F20038DC09 /* PreferencesWindow.swift in Sources */, F4D5A44A21E427BB006140DE /* Filter.swift in Sources */, F4F1A61D20D84CD1008B8C04 /* GithubClient.swift in Sources */, F4D5A45321E44BF1006140DE /* NewBuildFilter.swift in Sources */, @@ -554,26 +611,34 @@ 5394798D1FCB909600D6FA19 /* CircleCIClient.swift in Sources */, 539479871FCB8EA500D6FA19 /* CircleCIBuild.swift in Sources */, F4D5A44B21E427BB006140DE /* Filter.swift in Sources */, + F47EA5132254119B0008FE25 /* NewBuildFilterTest.swift in Sources */, F412CA9A21D3E3C90069C7C7 /* HTTPRequest.swift in Sources */, F4D5A45821E451BA006140DE /* TextPrinter.swift in Sources */, F4D5A45121E44BA6006140DE /* ClientBuildUpdater.swift in Sources */, + F4F4808321E51B1B0038DC09 /* PreferencesWindowController.swift in Sources */, F4D5A44421E4208A006140DE /* NSImage.swift in Sources */, + F4F4808721E51B510038DC09 /* ServerTable.swift in Sources */, F4F4807221E46CE90038DC09 /* NotificationListenerTest.swift in Sources */, F4D5A44221E42076006140DE /* SeaEyeBuildsController.swift in Sources */, + F4F4808421E51B2C0038DC09 /* PreferencesWindow.swift in Sources */, F4D5A43F21E4201F006140DE /* SeaEyeIconController.swift in Sources */, F4F4807421E471DE0038DC09 /* SettingsTest.swift in Sources */, F4F1A61E20D84DBE008B8C04 /* GithubClient.swift in Sources */, F4D5A44821E420C5006140DE /* Notifications.swift in Sources */, + F4917469221C4E0F00CAF9C1 /* ClientProject.swift in Sources */, 5389EF4A1FCBBA49001C0D5F /* DecodingTests.swift in Sources */, F422C54B20D48CF2002A5897 /* VersionNumberTests.swift in Sources */, F4D5A44621E420A4006140DE /* BuildDecorator.swift in Sources */, F412CA9B21D3E3D50069C7C7 /* SeaEyeDecoder.swift in Sources */, + F4F4808621E51B4D0038DC09 /* CurrentProjectsController.swift in Sources */, F4D5A43621E3F782006140DE /* FallbackView.swift in Sources */, F412CAA021D3EFEF0069C7C7 /* DateFormatter.swift in Sources */, F4D5A44321E4207C006140DE /* SeaEyeUpdatesController.swift in Sources */, + F47EA511225410480008FE25 /* ProjectTest.swift in Sources */, F4D5A45B21E45257006140DE /* SeaEyeStatusBarListener.swift in Sources */, F4D5A44021E42057006140DE /* SeaEyePopoverController.swift in Sources */, - F4D5A43321E3F6B3006140DE /* Settings.swift in Sources */, + F4F4808821E51B530038DC09 /* UnfollowedProjectsController.swift in Sources */, + F4D5A43321E3F6B3006140DE /* SettingsV0.swift in Sources */, F422C54920D48CDD002A5897 /* VersionNumber.swift in Sources */, F4F4807621E4DD6A0038DC09 /* PopoverContollerBuildUpdateListenerTest.swift in Sources */, F4D5A44521E42099006140DE /* BuildView.swift in Sources */, @@ -582,11 +647,12 @@ 5394798A1FCB908E00D6FA19 /* SeaEyeStatus.swift in Sources */, F4D5A44721E420B5006140DE /* ApplicationStartupManager.swift in Sources */, F4D5A43921E3FE32006140DE /* BuildSummary.swift in Sources */, - F4D5A44121E4206D006140DE /* SeaEyeSettingsController.swift in Sources */, 539479891FCB908E00D6FA19 /* Project.swift in Sources */, F4D5A43E21E41FEB006140DE /* SeaEyeStatusBar.swift in Sources */, + F4917466221C4CA600CAF9C1 /* SelectedServerView.swift in Sources */, F4D5A46421E4619C006140DE /* SeaEyeStatusBarListenerTest.swift in Sources */, F4D5A45421E44BF1006140DE /* NewBuildFilter.swift in Sources */, + F491746C221C4E4000CAF9C1 /* Settings.swift in Sources */, F4D5A45E21E454F4006140DE /* NotificationListener.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SeaEye/AppDelegate.swift b/SeaEye/AppDelegate.swift index 4f50189..1306e30 100644 --- a/SeaEye/AppDelegate.swift +++ b/SeaEye/AppDelegate.swift @@ -40,8 +40,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele fileprivate func initialSetup() { let userDefaults = UserDefaults.standard if userDefaults.bool(forKey: "SeaEyePerformedFirstSetup") == false { - userDefaults.set(true, forKey: Settings.Keys.notify.rawValue) - userDefaults.set(true, forKey: "SeaEyePerformedFirstSetup") +// userDefaults.set(true, forKey: SettingsV0.Keys.notify.rawValue) +// userDefaults.set(true, forKey: "SeaEyePerformedFirstSetup") ApplicationStartupManager.toggleLaunchAtStartup() } } diff --git a/SeaEye/circleci-normal-alt.png b/SeaEye/circleci-normal-alt.png deleted file mode 100644 index b79ecb23d2120ae1396d8d67c7279256ac0db93a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3075 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 Ro^}8L002ovPDHLkV1g=3!?XYZ diff --git a/SeaEye/circleci-normal.png b/SeaEye/circleci-normal.png deleted file mode 100644 index ba1a9c36cc13c325e1f57d13072504106c34dad6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3046 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 diff --git a/SeaEye/controllers/SeaEyeBuildsController.swift b/SeaEye/controllers/SeaEyeBuildsController.swift index 509b541..76ae801 100644 --- a/SeaEye/controllers/SeaEyeBuildsController.swift +++ b/SeaEye/controllers/SeaEyeBuildsController.swift @@ -30,9 +30,12 @@ class SeaEyeBuildsController: NSViewController, NSTableViewDelegate, NSTableView override func viewDidAppear() { self.reloadBuilds() } + func resetBuilds() { + buildsDict = Dictionary() + regenBuilds() + } func reloadBuilds(_: Any? = nil) { - print("Reload builds!") buildsTable?.reloadData() if fallbackView != nil { setupFallBackViews() diff --git a/SeaEye/controllers/SeaEyeIconController.swift b/SeaEye/controllers/SeaEyeIconController.swift index fd9a288..7270d84 100644 --- a/SeaEye/controllers/SeaEyeIconController.swift +++ b/SeaEye/controllers/SeaEyeIconController.swift @@ -42,11 +42,21 @@ class SeaEyeIconController: NSViewController { setup() } + func reset() { + popoverController.buildsViewController.resetBuilds() + for timer in self.timers { + timer.invalidate() + } + setup() + } + func setup() { + self.popoverController.iconController = self let secondsToRefreshBuilds = 30 let popoverControllerBuildListener = PopoverContollerBuildUpdateListener(buildSetter: popoverController) - let listeners: [BuildUpdateListener] = [TextPrinter(), + let listeners: [BuildUpdateListener] = [ +// TextPrinter(), popoverControllerBuildListener, SeaEyeStatusBarListener.init(statusBar: self.statusBarItem), NotificationListener()] diff --git a/SeaEye/controllers/SeaEyePopoverController.swift b/SeaEye/controllers/SeaEyePopoverController.swift index 829ba1f..96ae646 100644 --- a/SeaEye/controllers/SeaEyePopoverController.swift +++ b/SeaEye/controllers/SeaEyePopoverController.swift @@ -15,11 +15,11 @@ class SeaEyePopoverController: NSViewController, BuildSetter { @IBOutlet weak var openUpdatesButton: NSButton! @IBOutlet weak var shutdownButton: NSButton! - var settingsViewController: SeaEyeSettingsController! var buildsViewController: SeaEyeBuildsController! var updatesViewController: SeaEyeUpdatesController! var applicationStatus: SeaEyeStatus! var appDelegate: NSApplicationDelegate? = NSApplication.shared.delegate + var iconController: SeaEyeIconController? var heldBuilds = [CircleCIBuild]() override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { @@ -57,14 +57,12 @@ class SeaEyePopoverController: NSViewController, BuildSetter { fileprivate func setupViewControllers() { setupNibControllers() - settingsViewController.parentController = self updatesViewController.applicationStatus = self.applicationStatus openBuildsButton.isHidden = true subcontrollerView.addSubview(buildsViewController.view) } fileprivate func setupNibControllers() { - settingsViewController = SeaEyeSettingsController(nibName: "SeaEyeSettingsController", bundle: nil) buildsViewController = SeaEyeBuildsController(nibName: "SeaEyeBuildsController", bundle: nil) // If we have builds from client, we hold them until we can push them into the buildViewsController if heldBuilds.count > 0 { @@ -75,12 +73,12 @@ class SeaEyePopoverController: NSViewController, BuildSetter { } @IBAction func openSettings(_ sender: NSButton) { - openSettingsButton.isHidden = true - openUpdatesButton.isHidden = true - shutdownButton.isHidden = true - openBuildsButton.isHidden = false - buildsViewController.view.removeFromSuperview() - subcontrollerView.addSubview(settingsViewController.view) + NSApp.activate(ignoringOtherApps: true) // force the settings window to the front + + let prefrencesWindowVC = PreferencesWindowController() + prefrencesWindowVC.iconController = iconController + prefrencesWindowVC.showWindow(self) + iconController?.closePopover(nil) } @IBAction func openBuilds(_ sender: NSButton) { @@ -88,7 +86,6 @@ class SeaEyePopoverController: NSViewController, BuildSetter { openBuildsButton.isHidden = true shutdownButton.isHidden = false openSettingsButton.isHidden = false - settingsViewController.view.removeFromSuperview() updatesViewController.view.removeFromSuperview() subcontrollerView.addSubview(buildsViewController.view) } diff --git a/SeaEye/controllers/SeaEyeSettingsController.swift b/SeaEye/controllers/SeaEyeSettingsController.swift deleted file mode 100644 index 1401f20..0000000 --- a/SeaEye/controllers/SeaEyeSettingsController.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// SeaEyeSettingsController.swift -// SeaEye -// -// Created by Eoin Nolan on 26/10/2014. -// Copyright (c) 2014 Nolaneo. All rights reserved. -// - -import Cocoa - -class SeaEyeSettingsController: NSViewController { - - var parentController: SeaEyePopoverController! - - @IBOutlet weak var runOnStartup: NSButton! - @IBOutlet weak var showNotifications: NSButton! - - @IBOutlet weak var apiKeyField: NSTextField! - @IBOutlet weak var organizationField: NSTextField! - @IBOutlet weak var projectsField: NSTextField! - @IBOutlet weak var usersField: NSTextField! - @IBOutlet weak var branchesField: NSTextField! - - @IBOutlet weak var versionString: NSTextField! - - let appDelegate = NSApplication.shared.delegate - let settings = Settings.load() - - override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - } - - override func viewDidLoad() { - super.viewDidLoad() - setupVersionNumber() - } - - override func viewWillAppear() { - setupInputFields() - } - - @IBAction func openAPIPage(_ sender: NSButton) { - NSWorkspace.shared.open(URL(string: "https://circleci.com/account/api")!) - } - - @IBAction func saveUserData(_ sender: NSButton) { - let notify = showNotifications.state == .on - UserDefaults.standard.set(notify, forKey: Settings.Keys.notify.rawValue) - setUserDefaultsFromField(apiKeyField, key: Settings.Keys.apiKey.rawValue) - setUserDefaultsFromField(organizationField, key: Settings.Keys.organisation.rawValue) - setUserDefaultsFromField(projectsField, key: Settings.Keys.projects.rawValue) - setUserDefaultsFromField(branchesField, key: Settings.Keys.branchFilter.rawValue) - setUserDefaultsFromField(usersField, key: Settings.Keys.userFilter.rawValue) - NotificationCenter.default.post(name: Notification.Name(rawValue: "SeaEyeSettingsChanged"), object: nil) - parentController.openBuilds(sender) - } - - @IBAction func saveNotificationPreferences(_ sender: NSButton) { - let notify = showNotifications.state == .on - print("Notificaiton Preference: \(notify)") - UserDefaults.standard.set(notify, forKey: Settings.Keys.notify.rawValue) - } - - @IBAction func saveRunOnStartupPreferences(_ sender: NSButton) { - print("Changing launch on startup") - ApplicationStartupManager.toggleLaunchAtStartup() - } - - fileprivate func setUserDefaultsFromField(_ field: NSTextField, key: String) { - let userDefaults = UserDefaults.standard - let fieldValue = field.stringValue - if fieldValue.isEmpty { - userDefaults.removeObject(forKey: key) - } else { - userDefaults.setValue(fieldValue, forKey: key) - } - } - - fileprivate func setupInputFields() { - if settings.notify { - showNotifications.state = NSControl.StateValue.on - } else { - showNotifications.state = NSControl.StateValue.off - } - let hasRunOnStartup = ApplicationStartupManager.applicationIsInStartUpItems() - if hasRunOnStartup { - runOnStartup.state = NSControl.StateValue.on - } else { - runOnStartup.state = NSControl.StateValue.off - } - setupFieldFromUserDefaults(apiKeyField, key: Settings.Keys.apiKey.rawValue) - setupFieldFromUserDefaults(organizationField, key: Settings.Keys.organisation.rawValue) - setupFieldFromUserDefaults(projectsField, key: Settings.Keys.projects.rawValue) - setupFieldFromUserDefaults(branchesField, key: Settings.Keys.branchFilter.rawValue) - setupFieldFromUserDefaults(usersField, key: Settings.Keys.userFilter.rawValue) - } - - fileprivate func setupFieldFromUserDefaults(_ field: NSTextField, key: String) { - let userDefaults = UserDefaults.standard - if let savedValue = userDefaults.string(forKey: key) { - field.stringValue = savedValue - } - } - - fileprivate func setupVersionNumber() { - versionString.stringValue = VersionNumber.current().description - } -} diff --git a/SeaEye/controllers/preferences/CurrentProjectsController.swift b/SeaEye/controllers/preferences/CurrentProjectsController.swift new file mode 100644 index 0000000..2c57f59 --- /dev/null +++ b/SeaEye/controllers/preferences/CurrentProjectsController.swift @@ -0,0 +1,188 @@ +import Cocoa +import Foundation + +class CurrentProjectsController: NSObject, NSTextFieldDelegate { + fileprivate enum CellIdentifiers { + static let ProjectNameCell = "projectCell" + } + + private var projects: [Project] + private var selectedProjectIndex: Int? + + let cellIdentifer = NSUserInterfaceItemIdentifier(rawValue: CellIdentifiers.ProjectNameCell) + let tableView: NSTableView + let delegate: ProjectUnfollowedDelegate + let pUdelegate: ProjectUpdatedDelegate + + init(tableView: NSTableView, projects: [Project], delegate: ProjectUnfollowedDelegate, pUdelegate: ProjectUpdatedDelegate) { + self.projects = projects + self.tableView = tableView + self.delegate = delegate + self.pUdelegate = pUdelegate + + super.init() + tableView.delegate = self + tableView.dataSource = self + } + + func set(projects: [Project]) { + self.projects = projects + tableView.reloadData() + } + + func tableViewSelectionDidChange(_: Notification) { + print("hello") + updateStatus() + } + + func updateStatus() { + let itemsSelected = tableView.selectedRow + if itemsSelected >= 0 { + selectedProjectIndex = itemsSelected + print("Probably editing \(projects[itemsSelected]) \(tableView.selectedColumn)") + } else { + print("\(itemsSelected) no row is selected") + } + } + + func controlTextDidEndEditing(_ obj: Notification) { + if let textField = obj.object as? NSTextField { +// print("just set some value \(textField.tag) to \(textField.stringValue)") + let branchFilterTag = 1 + let userFilterTag = 2 + + if let index = self.selectedProjectIndex { + var project = projects[index] + if project.filter == nil { + project.filter = Filter(userFilter: nil, branchFilter: nil) + } + if textField.tag == branchFilterTag { + project.filter?.branchFilter = textField.stringValue + } + if textField.tag == userFilterTag { + project.filter?.userFilter = textField.stringValue + } + print(project) + projects[index] = project + pUdelegate.projectUpdated(projectIndex: index, project: project) + } else { + print("[WARN] there is no selectedProjectIndex") + } + } + tableView.reloadData() + } + + @objc func removeProject(_ sender: NSButton) { + let projectIndex = sender.tag + delegate.removeProject(projectIndex: projectIndex) + } + + @objc func notifyToggledSuccess(_ sender: NSButton) { + var project = projects[sender.tag] + project.notifySuccess = (sender.state == .on) + pUdelegate.projectUpdated(projectIndex: sender.tag, project: project) + + print("Success Notifications \(sender.tag) -> \(sender.state) \(project.notifySuccess)") + } + + @objc func notifyToggledFailure(_ sender: NSButton) { + var project = projects[sender.tag] + project.notifyFailure = (sender.state == .on) + pUdelegate.projectUpdated(projectIndex: sender.tag, project: project) + + print("Failure Notifications \(sender.tag) -> \(sender.state) \(project.notifyFailure)") + } +} + +extension CurrentProjectsController: NSTableViewDelegate { + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + if let cell = tableView.makeView(withIdentifier: cellIdentifer, owner: nil) as? NSTableCellView { + return tableViewForProjectCell(tableView, viewFor: tableColumn, row: row, cell: cell) + } + + return nil + } + + private func tableViewForProjectCell(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int, cell: NSTableCellView) -> NSView? { + var text: String = "unknown" + + let project = projects[row] + + if tableColumn == tableView.tableColumns[0] { + text = project.description + } + + if tableColumn == tableView.tableColumns[1] { + text = project.filter?.branchFilter ?? "*" + cell.textField?.tag = 1 + cell.textField?.isEditable = true + cell.textField?.delegate = self + } + if tableColumn == tableView.tableColumns[2] { + text = project.filter?.userFilter ?? "*" + cell.textField?.tag = 2 + + cell.textField?.isEditable = true + cell.textField?.delegate = self + } + if tableColumn == tableView.tableColumns[3] { + if #available(OSX 10.12, *) { + let button = NSButton(checkboxWithTitle: "", target: self, action: #selector(notifyToggledSuccess)) + button.tag = row + button.state = project.notifySuccess ? .on : .off + return button + + } else { + // Fallback on earlier versions + } + } + if tableColumn == tableView.tableColumns[4] { + if #available(OSX 10.12, *) { + let button = NSButton(checkboxWithTitle: "", target: self, action: #selector(notifyToggledFailure)) + button.tag = row + button.state = project.notifyFailure ? .on : .off + return button + + } else { + // Fallback on earlier versions + } + } + if tableColumn == tableView.tableColumns[5] { + if #available(OSX 10.12, *) { + let button = NSButton(title: "Remove", target: self, action: #selector(removeProject)) + button.setButtonType(NSButton.ButtonType.momentaryPushIn) + button.tag = row + return button + } + } + + cell.textField?.stringValue = text + return cell + } +} + +extension CurrentProjectsController: NSTableViewDataSource { + func numberOfRows(in _: NSTableView) -> Int { + return projects.count + } + + func tableView(_ view: NSTableView, + objectValueFor col: NSTableColumn?, + row index: Int) -> Any? { + let project = projects[index] + switch col?.identifier.rawValue { + case "projectName": + return project.description + case "branchFilter": + return project.filter?.branchFilter + case "userRegex": + return project.filter?.userFilter + case "notifySuccess": + return project.notifySuccess + case "notifyFailure": + return project.notifyFailure + default: + return nil + } + } +} diff --git a/SeaEye/controllers/preferences/PreferencesWindow.swift b/SeaEye/controllers/preferences/PreferencesWindow.swift new file mode 100644 index 0000000..d00890c --- /dev/null +++ b/SeaEye/controllers/preferences/PreferencesWindow.swift @@ -0,0 +1,149 @@ +import Cocoa +import Foundation + + +protocol ProjectFollowedDelegate { + func addProject(project: Project) +} + +protocol ProjectUnfollowedDelegate { + func removeProject(projectIndex: Int) +} + +protocol ProjectUpdatedDelegate { + func projectUpdated(projectIndex: Int, project: Project) +} + +class PreferencesWindow: NSWindow, NSWindowDelegate, NSTabViewDelegate, ProjectFollowedDelegate, ProjectUnfollowedDelegate, ProjectUpdatedDelegate { + // Preferences window + @IBOutlet var projectsTable: NSTableView! + @IBOutlet var versionNumber: NSTextField! + @IBOutlet var launchAtStartup: NSButton! + @IBOutlet var knownProjectsTable: NSTableView! + + private var serverListView: ServerTable? + private var currentProjectsView: CurrentProjectsController! + private var unfollowedProjectsTableView: UnfollowedProjectsController? + + // Servers + @IBOutlet var serverList: NSTableView! + @IBOutlet var apiServerApiPath: NSTextField! + @IBOutlet var apiServerAuthToken: NSSecureTextField! + @IBOutlet var apiServerTestButton: NSButton! + @IBOutlet var apiServerDeleteButton: NSButton! + + var settings = Settings.load() + var iconController: SeaEyeIconController? + + @IBAction func toggleStartup(_ sender: Any) { + print("Toggle Startup \(sender)") + if let button = sender as? NSButton { + print(button.state) + } + } + + func windowWillClose(_ notification: Notification) { + print("Closing window") + iconController?.reset() + } + + override func awakeFromNib() { + versionNumber.stringValue = VersionNumber.current().description + let selectedServerView = SelectedServerView(apiServerApiPath: apiServerApiPath, + apiServerAuthToken: apiServerAuthToken, + apiServerTestButton: apiServerTestButton) + + serverListView = ServerTable(tableView: serverList!, + clients: settings.clients(), + view: selectedServerView) + self.delegate = self + super.awakeFromNib() + } + + func addProject(project: Project) { + print("Add \(project)") + + if let index = self.serverListView?.selectedIndex { + print("Add Project \(index)") + settings.clientProjects[index].projects.append(project) + settings.save() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + serverListView?.reloadFromSettings() + iconController?.reset() + } + } + + func removeProject(projectIndex: Int) { + print("Remove Project \(projectIndex)") + + if let index = self.serverListView?.selectedIndex { + settings.clientProjects[index].projects.remove(at: projectIndex) + settings.save() + serverListView?.reloadFromSettings() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + iconController?.reset() + } + } + + func projectUpdated(projectIndex: Int, project: Project) { + print("Project @ \(projectIndex) updated") + + if let index = self.serverListView?.selectedIndex { + settings.clientProjects[index].projects[projectIndex] = project + settings.save() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + iconController?.reset() + } + + } + + func tabView(_: NSTabView, willSelect: NSTabViewItem?) { + if let item = willSelect { + if item.label == "Projects" { + if let client = self.serverListView?.selectedClient() { + currentProjectsView = CurrentProjectsController(tableView: projectsTable, + projects: settings.projects(), + delegate: self, + pUdelegate: self) + + unfollowedProjectsTableView = UnfollowedProjectsController(tableView: knownProjectsTable, + client: client, + knownProjects: settings.projects(), + delegate: self) + + } else { + print("There's no clients .... you can't select projects") + } + unfollowedProjectsTableView?.reload() + } + } + } + + @IBAction func testApiServerSelected(_: NSButton) { + print("test the api server") + serverListView?.testServer() + } + + @IBAction func addNewApiServerSelected(_: NSButton) { + let url = apiServerApiPath.stringValue + let apiKey = apiServerAuthToken.stringValue + var client = CircleCIClient.init(apiKey: apiKey) + client.baseURL = url + let clientp = ClientProject(client: client, projects: []) + + print("Actually adding now \(client)") + serverListView?.clients.append(client) + settings.clientProjects.append(clientp) + settings.save() + serverList.reloadData() + } + + @IBAction func deleteButtonPressed(_: Any) { + let selected = serverList.selectedRow + if selected >= 0 { + settings.clientProjects.remove(at: selected) + settings.save() + serverListView?.reloadFromSettings() + } + } +} diff --git a/SeaEye/controllers/preferences/PreferencesWindowController.swift b/SeaEye/controllers/preferences/PreferencesWindowController.swift new file mode 100644 index 0000000..519c04a --- /dev/null +++ b/SeaEye/controllers/preferences/PreferencesWindowController.swift @@ -0,0 +1,32 @@ +import Cocoa +import Foundation + +class PreferencesWindowController: NSWindowController { + var iconController: SeaEyeIconController? + + override init(window: NSWindow?) { + super.init(window: window) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + init(window: PreferencesWindow?, controller: SeaEyeIconController) { + self.iconController = controller + super.init(window: window) + window?.iconController = controller + } + + override var windowNibName: NSNib.Name { + return "PreferencesWindow" + } + + override func windowDidLoad() { + print("Prefrences window opened") + if let preferencesWindow = self.window as? PreferencesWindow { + preferencesWindow.iconController = iconController + } + super.windowDidLoad() + } +} diff --git a/SeaEye/controllers/preferences/ServerTable.swift b/SeaEye/controllers/preferences/ServerTable.swift new file mode 100644 index 0000000..28c54d6 --- /dev/null +++ b/SeaEye/controllers/preferences/ServerTable.swift @@ -0,0 +1,85 @@ +import Cocoa +import Foundation + +class ServerTable: NSObject { + let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "serverCell") + var selectedIndex: Int = 0 + var clients: [CircleCIClient] + var view: SelectedServerView + var tableView: NSTableView + + init(tableView: NSTableView, clients: [CircleCIClient], view: SelectedServerView) { + self.clients = clients + self.view = view + self.tableView = tableView + super.init() + tableView.delegate = self + tableView.dataSource = self + selectFirstClient() + } + + func testServer() { + if let client = selectedClient() { + client.getMe { response in + let alert = NSAlert() + alert.addButton(withTitle: "OK") + switch response { + case let .success(user): + alert.messageText = "This API server seems OK!" + alert.informativeText = "\(user.name)" + + case let .failure(err): + alert.messageText = "The test failed for \(client.baseURL)" + alert.informativeText = err.localizedDescription + } + + alert.runModal() + } + } + } + + func selectedClient() -> CircleCIClient? { + reloadFromSettings() + if clients.count > 0 && selectedIndex < clients.count { + return clients[self.selectedIndex] + } + return nil + } + + private func selectFirstClient() { + tableView.selectRowIndexes([0], byExtendingSelection: false) + + if let mclient = selectedClient() { + if clients.count > 0 { + view.fill(client: mclient) + } + } + } + + func reloadFromSettings() { + clients = Settings.load().clients() + tableView.reloadData() + } +} + +extension ServerTable : NSTableViewDataSource { + func numberOfRows(in _: NSTableView) -> Int { + return clients.count + } +} + +extension ServerTable : NSTableViewDelegate { + func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { + if let cell = tableView.makeView(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView { + cell.textField?.stringValue = clients[row].baseURL + return cell + } + return nil + } + + func tableView(_: NSTableView, shouldSelectRow row: Int) -> Bool { + print("\(row) was \(clients[row])") + view.fill(client: clients[row]) + return true + } +} diff --git a/SeaEye/controllers/preferences/UnfollowedProjectsController.swift b/SeaEye/controllers/preferences/UnfollowedProjectsController.swift new file mode 100644 index 0000000..34ab97b --- /dev/null +++ b/SeaEye/controllers/preferences/UnfollowedProjectsController.swift @@ -0,0 +1,79 @@ +import Cocoa +import Foundation + +class UnfollowedProjectsController: NSObject, NSTableViewDelegate, NSTableViewDataSource { + private let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "AddProjectName") + + private var client: CircleCIClient + private var projects: [Project] + private let tableView: NSTableView + private let delegate: ProjectFollowedDelegate + private var projectsAlreadyFollwed: [Project] + + init(tableView: NSTableView, client: CircleCIClient, knownProjects: [Project], delegate: ProjectFollowedDelegate) { + self.client = client + self.projectsAlreadyFollwed = knownProjects + projects = [] + self.delegate = delegate + self.tableView = tableView + super.init() + tableView.delegate = self + tableView.dataSource = self + reload() + } + + func reload() { + client.getProjects { response in + switch response { + case let .success(cip): + self.projects = cip.map { return $0.toProject() }.filter({ + for followedProject in self.projectsAlreadyFollwed { + if $0.description == followedProject.description { + return false + } + } + + return true + }) + self.tableView.reloadData() + case let .failure(err): + print(err.localizedDescription) + } + } + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + if let cell = tableView.makeView(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView { + if tableColumn == tableView.tableColumns[0] { + if row < projects.count { + cell.textField?.stringValue = projects[row].description + cell.textField?.tag = row + } + } + + if tableColumn == tableView.tableColumns[1] { + if #available(OSX 10.12, *) { + let button = NSButton(title: "Add project", target: self, action: #selector(addAProject)) + button.tag = row + return button + } else { + // Fallback on earlier versions + } + } + return cell + } + + return nil + } + + func numberOfRows(in _: NSTableView) -> Int { + return projects.count + } + + @objc func addAProject(_ sender: NSButton) { + let project = projects[sender.tag] + delegate.addProject(project: project) + projects.remove(at: sender.tag) + self.tableView.reloadData() + } +} diff --git a/SeaEye/listeners/NotificationListener.swift b/SeaEye/listeners/NotificationListener.swift index fd180af..ca7cf7a 100644 --- a/SeaEye/listeners/NotificationListener.swift +++ b/SeaEye/listeners/NotificationListener.swift @@ -9,21 +9,25 @@ class NotificationListener: BuildUpdateListener { func notify(project: Project, builds: [CircleCIBuild]) { let filteredBuilds = builds.filter {$0.lastUpdateTime() > initDate} - if project.notify { - if let summary = BuildSummary.generate(builds: filteredBuilds) { - switch summary.status { - case .running: - print("Running build ... skipping notify") - case .failed: + if let summary = BuildSummary.generate(builds: filteredBuilds) { + switch summary.status { + case .running: + print("Running build ... skipping notify") + case .failed: + if project.notifyFailure { print("Notify of a failed build") - let notification = BuildsNotification(summary.build!, summary.count).toNotification() notificationCenter.deliver(notification) - case .success: + } else { + print("Failed build, but project has failure notifications off") + } + case .success: + if project.notifySuccess { print("Notifiy of a success build") - let notification = BuildsNotification(summary.build!, summary.count).toNotification() notificationCenter.deliver(notification) + } else { + print("Success build, but project has success notifications off") } } } diff --git a/SeaEye/models/ClientProject.swift b/SeaEye/models/ClientProject.swift new file mode 100644 index 0000000..ef356b5 --- /dev/null +++ b/SeaEye/models/ClientProject.swift @@ -0,0 +1,6 @@ +import Foundation + +struct ClientProject: Codable { + var client: CircleCIClient + var projects: [Project] +} diff --git a/SeaEye/models/Project.swift b/SeaEye/models/Project.swift index 07e12a9..06a031f 100644 --- a/SeaEye/models/Project.swift +++ b/SeaEye/models/Project.swift @@ -5,7 +5,8 @@ struct Project: Codable, CustomStringConvertible { let organisation: String let name: String var filter: Filter? - var notify: Bool + var notifySuccess: Bool + var notifyFailure: Bool func path() -> String { return "\(vcsProvider)/\(organisation)/\(name)" diff --git a/SeaEye/models/Settings.swift b/SeaEye/models/Settings.swift index 01b03f8..dcc189f 100644 --- a/SeaEye/models/Settings.swift +++ b/SeaEye/models/Settings.swift @@ -1,76 +1,65 @@ import Foundation struct Settings: Codable { - enum Keys: String { - case apiKey = "SeaEyeAPIKey" - case organisation = "SeaEyeOrganization" - case projects = "SeaEyeProjects" - case branchFilter = "SeaEyeBranches" - case userFilter = "SeaEyeUsers" - case notify = "SeaEyeNotify" - } + static let defaultsKey: String = "SeaEyeSettings2" - var apiKey: String? - var organization: String? - var projectsString: String? - var branchFilter: String? - var userFilter: String? - var notify: Bool = true + var clientProjects: [ClientProject] static func load(userDefaults: UserDefaults = UserDefaults.standard) -> Settings { - var settings = self.init() - settings.apiKey = userDefaults.string(forKey: Keys.apiKey.rawValue) - settings.organization = userDefaults.string(forKey: Keys.organisation.rawValue) - settings.projectsString = userDefaults.string(forKey: Keys.projects.rawValue) - settings.branchFilter = userDefaults.string(forKey: Keys.branchFilter.rawValue) - settings.userFilter = userDefaults.string(forKey: Keys.userFilter.rawValue) - settings.notify = userDefaults.bool(forKey: Keys.notify.rawValue) + var settings = Settings(clientProjects: []) + if let settingsString = userDefaults.string(forKey: self.defaultsKey) { + let decoder = JSONDecoder() + if let data = settingsString.data(using: .utf8) { + do { + settings = try decoder.decode(Settings.self, from: data) + return settings + } catch let error { + print(error.localizedDescription) + } + } + } else { + let oldSettings = SettingsV0.load(userDefaults: userDefaults) + print("Migrating old settings to new settings format") + settings = oldSettings.toSettings() + settings.save(userDefaults: userDefaults) + } return settings } - func projects() -> [ClientProject] { - let projectNames = projectsString?.components(separatedBy: CharacterSet.whitespaces) - if (apiKey == nil) { - return [] + func save(userDefaults: UserDefaults = UserDefaults.standard) { + for client in clientProjects { + print("Saving \(client)") } - let client = CircleCIClient.init(apiKey: apiKey!) - if !valid() { - return [] - } - var projects = [Project]() - if let projectNames = projectNames { - projects = projectNames.map { - let filter = Filter.init(userFilter: userFilter, branchFilter: branchFilter) - // github is hardcoded, as it was assumed - return Project.init(vcsProvider: "github", - organisation: self.organization!, - name: $0, - filter: filter, - notify: self.notify) - } + let jsonEncoder = JSONEncoder() + + do { + let jsonData = try jsonEncoder.encode(self) + let json = String(data: jsonData, encoding: String.Encoding.utf8) + userDefaults.setValue(json, forKey: Settings.defaultsKey) + } catch let error { + print(error.localizedDescription) } - return [ClientProject.init(client: client, projects: projects)] } func clientBuildUpdateListeners(listeners: [BuildUpdateListener]) -> [ClientBuildUpdater] { - let clientProjects = projects() return clientProjects.flatMap { (cp) -> [ClientBuildUpdater] in - cp.projects.map { - return ClientBuildUpdater(listeners: listeners, - client: cp.client, - project: $0) - } + return cp.projects.map({ + ClientBuildUpdater(listeners: listeners, + client: cp.client, + project: $0) + }) } } - func valid() -> Bool { - return apiKey != nil && organization != nil && projectsString != nil + func clients() -> [CircleCIClient] { + return self.clientProjects.map { $0.client} + } + func projects() -> [Project] { + return Array(clientProjects.map {$0.projects}.joined()) + } + func numberOfProjects() -> Int { + return clientProjects.compactMap { $0.projects.count }.reduce(0, { $0 + $1 }) } -} - -struct ClientProject: Codable { - let client: CircleCIClient - let projects: [Project] } diff --git a/SeaEye/models/SettingsV0.swift b/SeaEye/models/SettingsV0.swift new file mode 100644 index 0000000..dd1f08a --- /dev/null +++ b/SeaEye/models/SettingsV0.swift @@ -0,0 +1,90 @@ +import Foundation + +struct SettingsV0: Codable { + enum Keys: String { + case apiKey = "SeaEyeAPIKey" + case organisation = "SeaEyeOrganization" + case projects = "SeaEyeProjects" + case branchFilter = "SeaEyeBranches" + case userFilter = "SeaEyeUsers" + case notify = "SeaEyeNotify" + } + + var apiKey: String? + var organization: String? + var projectsString: String? + var branchFilter: String? + var userFilter: String? + var notify: Bool = true + + static func load(userDefaults: UserDefaults = UserDefaults.standard) -> SettingsV0 { + var settings = self.init() + settings.apiKey = userDefaults.string(forKey: Keys.apiKey.rawValue) + settings.organization = userDefaults.string(forKey: Keys.organisation.rawValue) + settings.projectsString = userDefaults.string(forKey: Keys.projects.rawValue) + settings.branchFilter = userDefaults.string(forKey: Keys.branchFilter.rawValue) + settings.userFilter = userDefaults.string(forKey: Keys.userFilter.rawValue) + settings.notify = userDefaults.bool(forKey: Keys.notify.rawValue) + + return settings + } + + func save(userDefaults: UserDefaults = UserDefaults.standard) { + userDefaults.set(self.apiKey, forKey: Keys.apiKey.rawValue) + userDefaults.set(self.organization, forKey: Keys.organisation.rawValue) + userDefaults.set(self.projectsString, forKey: Keys.projects.rawValue) + userDefaults.set(self.branchFilter, forKey: Keys.branchFilter.rawValue) + userDefaults.set(self.userFilter, forKey: Keys.userFilter.rawValue) + userDefaults.set(self.notify, forKey: Keys.notify.rawValue) + } + + func toSettings() -> Settings { + return Settings(clientProjects: self.clientProjects()) + } + + func clientProjects() -> [ClientProject] { + if !valid() { + return [] + } + let projectNames = projectsString?.components(separatedBy: CharacterSet.whitespaces) + let client = CircleCIClient.init(apiKey: apiKey!) + var projects = [Project]() + + if let projectNames = projectNames { + projects = projectNames.map { + let filter = Filter.init(userFilter: userFilter, branchFilter: branchFilter) + // github is hardcoded, as it was assumed + return Project.init(vcsProvider: "github", + organisation: self.organization!, + name: $0, + filter: filter, + notifySuccess: self.notify, + notifyFailure: self.notify + ) + } + } + return [ClientProject.init(client: client, projects: projects)] + } + + func clients() -> [CircleCIClient] { + return self.clientProjects().map { $0.client} + } + func projects() -> [Project] { + return Array(clientProjects().map {$0.projects}.joined()) + } + + func clientBuildUpdateListeners(listeners: [BuildUpdateListener]) -> [ClientBuildUpdater] { + let clientProjects = self.clientProjects() + return clientProjects.flatMap { (cp) -> [ClientBuildUpdater] in + cp.projects.map { + return ClientBuildUpdater(listeners: listeners, + client: cp.client, + project: $0) + } + } + } + + func valid() -> Bool { + return apiKey != nil && organization != nil && projectsString != nil + } +} diff --git a/SeaEye/networking/CircleCIClient.swift b/SeaEye/networking/CircleCIClient.swift index 2b9593f..6ab96d6 100644 --- a/SeaEye/networking/CircleCIClient.swift +++ b/SeaEye/networking/CircleCIClient.swift @@ -3,6 +3,27 @@ import Foundation protocol BuildClient { func getProject(name: String, completion: ((Result<[CircleCIBuild]>) -> Void)?) } +struct CircleCIProject: Decodable { + let username: String + let reponame: String + let vcsUrl: String + let following: Bool + + func description() -> String { + return "\(username)/\(reponame)" + } + + func toProject() -> Project { + return Project( + vcsProvider: "github", + organisation: username, + name: reponame, + filter: nil, + notifySuccess: true, + notifyFailure: true + ) + } +} struct CircleCIClient: Codable, BuildClient { var baseURL: String = "https://circleci.com" @@ -11,6 +32,11 @@ struct CircleCIClient: Codable, BuildClient { init(apiKey: String) { token = apiKey } + + func getProjects(completion: ((Result<[CircleCIProject]>) -> Void)?) { + request(get(path: "projects"), of: [CircleCIProject].self, completion: completion) + } + func getProject(name: String, completion: ((Result<[CircleCIBuild]>) -> Void)?) { request(get(path: "project/\(name)"), of: [CircleCIBuild].self, completion: completion) } diff --git a/SeaEye/views/FallbackView.swift b/SeaEye/views/FallbackView.swift index 54f5885..e4c344d 100644 --- a/SeaEye/views/FallbackView.swift +++ b/SeaEye/views/FallbackView.swift @@ -5,15 +5,18 @@ struct FallbackView { let builds: [CircleCIBuild] func description() -> String? { - if settings.apiKey == nil { - return "You have not set an API key" - } - if settings.organization == nil { - return "You have not set an organization name" - } - if settings.projectsString == nil { - return "You have not added any projects" + if settings.numberOfProjects() == 0 { + return "Add some projects, mo chara" } +// if settings.apiKey == nil { +// return "You have not set an API key" +// } +// if settings.organization == nil { +// return "You have not set an organization name" +// } +// if settings.projectsString == nil { +// return "You have not added any projects" +// } if builds.count == 0 { return "No Recent Builds Found" } diff --git a/SeaEye/views/PreferencesWindow.xib b/SeaEye/views/PreferencesWindow.xib new file mode 100644 index 0000000..cfc9669 --- /dev/null +++ b/SeaEye/views/PreferencesWindow.xib @@ -0,0 +1,569 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Personal +Access +Token + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You can use 'Test' to verify your settings and move to 'Projects' from above, or if you are an advanced user, you can add more servers to the left. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeaEye/views/SeaEyeSettingsController.xib b/SeaEye/views/SeaEyeSettingsController.xib deleted file mode 100644 index 9d59557..0000000 --- a/SeaEye/views/SeaEyeSettingsController.xib +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - diff --git a/SeaEye/views/SelectedServerView.swift b/SeaEye/views/SelectedServerView.swift new file mode 100644 index 0000000..473ca30 --- /dev/null +++ b/SeaEye/views/SelectedServerView.swift @@ -0,0 +1,14 @@ +import Foundation +import Cocoa + +struct SelectedServerView { + var apiServerApiPath: NSTextField! + var apiServerAuthToken: NSTextField! + var apiServerTestButton: NSButton! + + func fill(client: CircleCIClient) { + apiServerApiPath.stringValue = client.baseURL + apiServerAuthToken.stringValue = client.token + apiServerTestButton.isEnabled = !client.token.isEmpty + } +} diff --git a/SeaEyeTests/models/FilterTest.swift b/SeaEyeTests/FilterTest.swift similarity index 99% rename from SeaEyeTests/models/FilterTest.swift rename to SeaEyeTests/FilterTest.swift index 144c4fc..401880f 100644 --- a/SeaEyeTests/models/FilterTest.swift +++ b/SeaEyeTests/FilterTest.swift @@ -1,4 +1,4 @@ -import Cocoa +import Foundation import XCTest class FilterTest: XCTestCase { diff --git a/SeaEyeTests/NewBuildFilterTest.swift b/SeaEyeTests/NewBuildFilterTest.swift new file mode 100644 index 0000000..ebe059b --- /dev/null +++ b/SeaEyeTests/NewBuildFilterTest.swift @@ -0,0 +1,57 @@ +import XCTest +class NewBuildFilterTest: XCTestCase { + func testItAlsoSortsBuilds() { + var filter = NewBuildFilter() + let project = Project.init(vcsProvider: "github", + organisation: "nolaneo", + name: "SeaEye", + filter: nil, + notifySuccess: false, + notifyFailure: false) + var updatedBuilds = filter.newBuilds(project: project, builds: []) + let build = CircleCIBuild.init(branch: "master", + project: "ugh", + status: .failed, + subject: "You done goofed", + user: "Mongey", + buildNum: 1, + url: URL.init(string: "http://google.com")!, + date: Date.init(timeIntervalSinceNow: 0)) + let newerBuild = CircleCIBuild.init(branch: "master", + project: "ugh", + status: .success, + subject: "You done goofed", + user: "Mongey", + buildNum: 1, + url: URL.init(string: "http://google.com")!, + date: Date.init(timeIntervalSinceNow: 1)) + + updatedBuilds = filter.newBuilds(project: project, builds: [build, newerBuild]) + XCTAssertEqual(updatedBuilds[0].status, newerBuild.status) + XCTAssertEqual(updatedBuilds[1].status, build.status) + } + + func testItFiltersBuildsSoWeOnlySeeNewOnes() { + var filter = NewBuildFilter() + let project = Project.init(vcsProvider: "github", + organisation: "nolaneo", + name: "SeaEye", + filter: nil, + notifySuccess: false, + notifyFailure: false) + var updatedBuilds = filter.newBuilds(project: project, builds: []) + let build = CircleCIBuild.init(branch: "master", + project: "ugh", + status: .failed, + subject: "You done goofed", + user: "Mongey", + buildNum: 1, + url: URL.init(string: "http://google.com")!, + date: Date.init(timeIntervalSinceNow: 0)) + updatedBuilds = filter.newBuilds(project: project, builds: [build]) + XCTAssertEqual(updatedBuilds.count, 1) + + updatedBuilds = filter.newBuilds(project: project, builds: [build]) + XCTAssertEqual(updatedBuilds.count, 0) + } +} diff --git a/SeaEyeTests/NotificationListenerTest.swift b/SeaEyeTests/NotificationListenerTest.swift index 0aaf9c7..398c314 100644 --- a/SeaEyeTests/NotificationListenerTest.swift +++ b/SeaEyeTests/NotificationListenerTest.swift @@ -25,7 +25,8 @@ class NotificationListenerTest: XCTestCase { organisation: "nolaneo", name: "SeaEye", filter: nil, - notify: true) + notifySuccess: true, + notifyFailure: true) sut.notify(project: project, builds: [failed]) } diff --git a/SeaEyeTests/SettingsTest.swift b/SeaEyeTests/SettingsTest.swift index 255dade..a4a6f21 100644 --- a/SeaEyeTests/SettingsTest.swift +++ b/SeaEyeTests/SettingsTest.swift @@ -10,14 +10,53 @@ import Foundation import XCTest class SettingsTest: XCTestCase { + func testMigrationofV0toV1WhenV1DoesNotExist() { + let ud = UserDefaults.init(suiteName: UUID().uuidString)! + let oldSettings = SettingsV0.init(apiKey: "abc123", + organization: "nolaneo", + projectsString: "SeaEye foobar", + branchFilter: "master", + userFilter: nil, + notify: true) + oldSettings.save(userDefaults: ud) + XCTAssertNil(ud.string(forKey: Settings.defaultsKey)) - func testDefaults() { - var settings = Settings.load(userDefaults: UserDefaults.init(suiteName: "testing")!) - XCTAssertFalse(settings.notify) - XCTAssertFalse(settings.valid()) - settings.apiKey = "abc" - settings.organization = "nolaneo" - settings.projectsString = "SeaEye" - XCTAssertTrue(settings.valid()) + let result = Settings.load(userDefaults: ud) + XCTAssertEqual(result.clientProjects.count, 1, "Should have been a migration") + + // we now expect to see a value in Settings + XCTAssertNotNil(ud.string(forKey: Settings.defaultsKey)) + + XCTAssertEqual(result.clientProjects[0].client.baseURL, "https://circleci.com") + XCTAssertEqual(result.clientProjects[0].client.token, "abc123") + XCTAssertEqual(result.clientProjects[0].projects[0].organisation, "nolaneo") + XCTAssertEqual(result.clientProjects[0].projects[0].name, "SeaEye") + XCTAssertEqual(result.clientProjects[0].projects[1].name, "foobar") + } + + func testLoadingOfSettingsWhenV0AndV1Exist() { + let ud = UserDefaults.init(suiteName: UUID().uuidString)! + let oldSettings = SettingsV0.init(apiKey: "abc123", + organization: "nolaneo", + projectsString: "SeaEye foobar", + branchFilter: "master", + userFilter: nil, + notify: true) + + oldSettings.save(userDefaults: ud) + let newSettings = Settings.init(clientProjects: []) + newSettings.save(userDefaults: ud) + XCTAssertNotNil(ud.string(forKey: Settings.defaultsKey)) + let result = Settings.load(userDefaults: ud) + // we should ignore v0's key, we have v1 + XCTAssertEqual(result.clientProjects.count, 0) + } + + func testInvalidJSONInUserDefaults() { + let ud = UserDefaults.init(suiteName: UUID().uuidString)! + XCTAssertNil(ud.string(forKey: Settings.defaultsKey)) + ud.setValue("not a json string", forKey: Settings.defaultsKey) + let newSettings = Settings.load(userDefaults: ud) + XCTAssertEqual(newSettings.clientProjects.count, 0, "Settings should ignore invalid json") } } diff --git a/SeaEyeTests/listeners/PopoverContollerBuildUpdateListenerTest.swift b/SeaEyeTests/listeners/PopoverContollerBuildUpdateListenerTest.swift index 279453f..3fde63b 100644 --- a/SeaEyeTests/listeners/PopoverContollerBuildUpdateListenerTest.swift +++ b/SeaEyeTests/listeners/PopoverContollerBuildUpdateListenerTest.swift @@ -36,7 +36,8 @@ class PopoverContollerBuildUpdateListenerTest: XCTestCase { organisation: "nolaneo", name: "SeaEye", filter: nil, - notify: true) + notifySuccess: true, + notifyFailure: true) sut.notify(project: project, builds: [old, new]) diff --git a/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift b/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift index 8a75524..2944352 100644 --- a/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift +++ b/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift @@ -31,7 +31,8 @@ class SeaEyeStatusBarListenerTest: XCTestCase { organisation: "nolaneo", name: "SeaEye", filter: nil, - notify: true) + notifySuccess: true, + notifyFailure: true) sut.notify(project: project, builds: [failed]) XCTAssertEqual(sut.statusBar.state, .idle, "Old builds should not effect the status") @@ -60,5 +61,16 @@ class SeaEyeStatusBarListenerTest: XCTestCase { sut.notify(project: project, builds: []) XCTAssertEqual(sut.statusBar.state, .success, "The Icon should remain set if there are no builds") + + let runningBuild = CircleCIBuild(branch: "master", + project: "foo/bar", + status: .running, + subject: "Got some tests", + user: "Homer", + buildNum: 100, + url: URL.init(string: "https://google.com")!, + date: Date()) + sut.notify(project: project, builds: [runningBuild]) + XCTAssertEqual(sut.statusBar.state, .running, "The Icon should set to running, when there's a running build") } } diff --git a/SeaEyeTests/models/ProjectTest.swift b/SeaEyeTests/models/ProjectTest.swift new file mode 100644 index 0000000..1b883b9 --- /dev/null +++ b/SeaEyeTests/models/ProjectTest.swift @@ -0,0 +1,15 @@ +import Foundation +import XCTest + +class ProjectTest: XCTestCase { + func testProjectPath() { + let project = Project.init(vcsProvider: "github", + organisation: "nolaneo", + name: "SeaEye", + filter: nil, + notifySuccess: false, + notifyFailure: false) + + XCTAssertEqual(project.path(), "github/nolaneo/SeaEye") + } +}