From 96035291bb360a9bb36363ea451d4c042fbb4149 Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Mon, 11 Jan 2016 23:49:56 -0800 Subject: [PATCH 01/10] Update readme. --- Readme.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.markdown b/Readme.markdown index 402a924..a20f7ab 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -9,7 +9,7 @@ Cloudy uses [Carthage](https://github.com/carthage/carthage) to manage framework ``` $ git clone https://github.com/calebd/cloudy $ cd cloudy -$ carthage update +$ carthage bootstrap --platform mac --no-use-binaries $ open Cloudy.xcodeproj ``` From 6470f5c3cb19598441df466331c0f115288f3e51 Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Mon, 11 Jan 2016 23:50:16 -0800 Subject: [PATCH 02/10] Update project. --- Cartfile | 4 ++-- Cartfile.resolved | 5 +++-- Cloudy.xcodeproj/project.pbxproj | 12 +++++++++--- .../xcshareddata/xcschemes/Cloudy.xcscheme | 13 ++++++++----- Cloudy/Info.plist | 2 +- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Cartfile b/Cartfile index 8551d3a..7f7fc57 100644 --- a/Cartfile +++ b/Cartfile @@ -1,3 +1,3 @@ -github "ReactiveCocoa/ReactiveCocoa" == 2.4.7 -github "sparkle-project/Sparkle" == 1.9.0 +github "ReactiveCocoa/ReactiveCocoa" "v4.0.0-RC.1" +github "sparkle-project/Sparkle" ~> 1.9.0 github "calebd/MediaKeys" == 0.1.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index aecaa19..96f3e90 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,4 @@ github "calebd/MediaKeys" "v0.1.0" -github "ReactiveCocoa/ReactiveCocoa" "v2.4.7" -github "sparkle-project/Sparkle" "1.9.0" +github "antitypical/Result" "1.0.1" +github "sparkle-project/Sparkle" "1.13.0" +github "ReactiveCocoa/ReactiveCocoa" "v4.0.0-RC.1" diff --git a/Cloudy.xcodeproj/project.pbxproj b/Cloudy.xcodeproj/project.pbxproj index e945c11..61e044a 100644 --- a/Cloudy.xcodeproj/project.pbxproj +++ b/Cloudy.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3AE739B31C44E7350004A7A5 /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE739B11C44E5F40004A7A5 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3B0206EC1AE7461400E45D53 /* NowPlayingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B0206EB1AE7461400E45D53 /* NowPlayingController.swift */; }; 3B1A19201B4E681A005CE283 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BA5FE361AF35A8E00A5622B /* Sparkle.framework */; }; 3B430E261AE0A88F00AEBFBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B430E251AE0A88F00AEBFBC /* AppDelegate.swift */; }; @@ -31,6 +32,7 @@ files = ( 3BB0D1B91AE2263100AB4A33 /* ReactiveCocoa.framework in Embed Frameworks */, 3BFB222C1AFA071300127AF1 /* MediaKeys.framework in Embed Frameworks */, + 3AE739B31C44E7350004A7A5 /* Result.framework in Embed Frameworks */, 3BA5FE391AF35A9800A5622B /* Sparkle.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -39,6 +41,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 3AE739B11C44E5F40004A7A5 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/Mac/Result.framework; sourceTree = ""; }; 3B0206EB1AE7461400E45D53 /* NowPlayingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NowPlayingController.swift; sourceTree = ""; }; 3B2332D41AE1BA48005C6654 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveCocoa.framework; path = Carthage/Build/Mac/ReactiveCocoa.framework; sourceTree = ""; }; 3B430E201AE0A88F00AEBFBC /* Cloudy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cloudy.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -70,6 +73,7 @@ 3B2332D81AE1BA84005C6654 /* Frameworks */ = { isa = PBXGroup; children = ( + 3AE739B11C44E5F40004A7A5 /* Result.framework */, 3BFB22291AFA070C00127AF1 /* MediaKeys.framework */, 3BA5FE361AF35A8E00A5622B /* Sparkle.framework */, 3B2332D41AE1BA48005C6654 /* ReactiveCocoa.framework */, @@ -146,7 +150,8 @@ 3B430E181AE0A88F00AEBFBC /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0630; + LastSwiftUpdateCheck = 0720; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Caleb Davenport"; TargetAttributes = { 3B430E1F1AE0A88F00AEBFBC = { @@ -239,6 +244,7 @@ COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -301,7 +307,6 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -310,6 +315,7 @@ INFOPLIST_FILE = Cloudy/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; OTHER_SWIFT_FLAGS = "-DDEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.calebd.cloudy; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -318,7 +324,6 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -326,6 +331,7 @@ ); INFOPLIST_FILE = Cloudy/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.calebd.cloudy; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/Cloudy.xcodeproj/xcshareddata/xcschemes/Cloudy.xcscheme b/Cloudy.xcodeproj/xcshareddata/xcschemes/Cloudy.xcscheme index 1855b63..9ff1c76 100644 --- a/Cloudy.xcodeproj/xcshareddata/xcschemes/Cloudy.xcscheme +++ b/Cloudy.xcodeproj/xcshareddata/xcschemes/Cloudy.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -38,15 +38,18 @@ ReferencedContainer = "container:Cloudy.xcodeproj"> + + @@ -62,10 +65,10 @@ diff --git a/Cloudy/Info.plist b/Cloudy/Info.plist index 70b327f..81d3e1e 100644 --- a/Cloudy/Info.plist +++ b/Cloudy/Info.plist @@ -9,7 +9,7 @@ CFBundleIconFile CFBundleIdentifier - com.calebd.cloudy + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName From 6107d8e479ca9f0d5882668c380ff3c80a98e811 Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Mon, 11 Jan 2016 23:50:21 -0800 Subject: [PATCH 03/10] Make things build for now. --- Cloudy/AppDelegate.swift | 102 ++++----- Cloudy/Base.lproj/Main.storyboard | 12 +- Cloudy/MainWindowController.swift | 112 +++++----- Cloudy/PlaybackViewController.swift | 318 +++++++++++++++------------- 4 files changed, 279 insertions(+), 265 deletions(-) diff --git a/Cloudy/AppDelegate.swift b/Cloudy/AppDelegate.swift index 05a6d9f..c735eb8 100644 --- a/Cloudy/AppDelegate.swift +++ b/Cloudy/AppDelegate.swift @@ -8,64 +8,64 @@ import Cocoa -private func DockMenu(#item: PlaybackItem?, #playing: Bool) -> NSMenu? { - if item == nil { - return nil - } - - let menu = NSMenu() - menu.autoenablesItems = false - - let nowPlayingItem = NSMenuItem() - nowPlayingItem.title = "Now Playing" - nowPlayingItem.enabled = false - menu.addItem(nowPlayingItem) - - let showNameItem = NSMenuItem() - showNameItem.title = item?.show ?? "Now Show Title" - showNameItem.indentationLevel = 1 - showNameItem.enabled = false - menu.addItem(showNameItem) - - let episodeNameItem = NSMenuItem() - episodeNameItem.title = item?.episode ?? "Now Episode Title" - episodeNameItem.indentationLevel = 1 - episodeNameItem.enabled = false - menu.addItem(episodeNameItem) - - menu.addItem(NSMenuItem.separatorItem()) - - let togglePlaybackItem = NSMenuItem(title: playing ? "Pause" : "Play", action: "togglePlaybackState:", keyEquivalent: "") - menu.addItem(togglePlaybackItem) - - return menu -} - -private func GetPlaybackViewController() -> PlaybackViewController? { - let windows = NSApplication.sharedApplication().windows as? [NSWindow] ?? [] - for window in windows { - if let controller = window.contentViewController as? PlaybackViewController { - return controller - } - } - return nil -} +//private func DockMenu(#item: PlaybackItem?, #playing: Bool) -> NSMenu? { +// if item == nil { +// return nil +// } +// +// let menu = NSMenu() +// menu.autoenablesItems = false +// +// let nowPlayingItem = NSMenuItem() +// nowPlayingItem.title = "Now Playing" +// nowPlayingItem.enabled = false +// menu.addItem(nowPlayingItem) +// +// let showNameItem = NSMenuItem() +// showNameItem.title = item?.show ?? "Now Show Title" +// showNameItem.indentationLevel = 1 +// showNameItem.enabled = false +// menu.addItem(showNameItem) +// +// let episodeNameItem = NSMenuItem() +// episodeNameItem.title = item?.episode ?? "Now Episode Title" +// episodeNameItem.indentationLevel = 1 +// episodeNameItem.enabled = false +// menu.addItem(episodeNameItem) +// +// menu.addItem(NSMenuItem.separatorItem()) +// +// let togglePlaybackItem = NSMenuItem(title: playing ? "Pause" : "Play", action: "togglePlaybackState:", keyEquivalent: "") +// menu.addItem(togglePlaybackItem) +// +// return menu +//} + +//private func GetPlaybackViewController() -> PlaybackViewController? { +// let windows = NSApplication.sharedApplication().windows as? [NSWindow] ?? [] +// for window in windows { +// if let controller = window.contentViewController as? PlaybackViewController { +// return controller +// } +// } +// return nil +//} @NSApplicationMain final class AppDelegate: NSObject, NSApplicationDelegate { // MARK: - NSApplicationDelegate - func applicationDockMenu(sender: NSApplication) -> NSMenu? { - return DockMenu( - item: NowPlayingController.shared().nowPlayingItem, - playing: NowPlayingController.shared().playing - ) - } +// func applicationDockMenu(sender: NSApplication) -> NSMenu? { +// return DockMenu( +// item: NowPlayingController.shared().nowPlayingItem, +// playing: NowPlayingController.shared().playing +// ) +// } // MARK: - Private - @objc private func togglePlaybackState(sender: AnyObject?) { - GetPlaybackViewController()?.togglePlaybackState(self) - } +// @objc private func togglePlaybackState(sender: AnyObject?) { +// GetPlaybackViewController()?.togglePlaybackState(self) +// } } diff --git a/Cloudy/Base.lproj/Main.storyboard b/Cloudy/Base.lproj/Main.storyboard index 04def86..b8bdac3 100644 --- a/Cloudy/Base.lproj/Main.storyboard +++ b/Cloudy/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -666,7 +666,7 @@ - + @@ -765,20 +765,20 @@ - + - + - + diff --git a/Cloudy/MainWindowController.swift b/Cloudy/MainWindowController.swift index b55b482..163c8b0 100644 --- a/Cloudy/MainWindowController.swift +++ b/Cloudy/MainWindowController.swift @@ -27,62 +27,62 @@ final class MainWindowController: NSWindowController, NSWindowDelegate { override func windowDidLoad() { super.windowDidLoad() - window?.titleVisibility = .Hidden - window?.excludedFromWindowsMenu = true - shareButton?.sendActionOn(Int(NSEventMask.LeftMouseDownMask.rawValue)) - playbackButton?.imagePosition = .ImageLeft - - let nowPlayingItemSignal = NowPlayingController.shared().rac_valuesForKeyPath("nowPlayingItem", observer: self) - let hasNowPlayingItemSignal = nowPlayingItemSignal.map({ $0 is PlaybackItem }) - let isPlayingSignal = NowPlayingController.shared().rac_valuesForKeyPath("playing", observer: self) - - hasNowPlayingItemSignal.setKeyPath("shareButton.enabled", onObject: self) - hasNowPlayingItemSignal.setKeyPath("playbackButton.enabled", onObject: self) - - navigationControl?.rac_liftSelector("setEnabled:forSegment:", withSignalsFromArray: [ - rac_valuesForKeyPath("contentViewController.webView.canGoBack", observer: self), - RACSignal.`return`(0) - ]) - - navigationControl?.rac_liftSelector("setEnabled:forSegment:", withSignalsFromArray: [ - rac_valuesForKeyPath("contentViewController.webView.canGoForward", observer: self), - RACSignal.`return`(1) - ]) - - nowPlayingItemSignal - .map({ - let item = $0 as? PlaybackItem - return item?.prettyName() ?? "Cloudy: Nothing Playing" - }) - .setKeyPath("playbackButton.title", onObject: self) - - nowPlayingItemSignal - .map({ - let item = $0 as? PlaybackItem - return item?.prettyName() ?? "Cloudy" - }) - .setKeyPath("window.title", onObject: self) - - let playbackButtonImageSignals = [ hasNowPlayingItemSignal, isPlayingSignal ] - RACSignal.combineLatest(playbackButtonImageSignals) - .map({ - let tuple = $0 as! RACTuple - let hasNowPlayingItem = tuple.first as! Bool - let isPlaying = tuple.second as! Bool - switch (hasNowPlayingItem, isPlaying) { - case (true, true): - return NSImage(named: "pause") - case (true, false): - return NSImage(named: "play") - default: - return nil - } - }) - .setKeyPath("playbackButton.image", onObject: self) - - rac_liftSelector("webViewLoadingDidChange:", withSignalsFromArray: [ - rac_valuesForKeyPath("contentViewController.webView.loading", observer: self) - ]) +// window?.titleVisibility = .Hidden +// window?.excludedFromWindowsMenu = true +// shareButton?.sendActionOn(Int(NSEventMask.LeftMouseDownMask.rawValue)) +// playbackButton?.imagePosition = .ImageLeft +// +// let nowPlayingItemSignal = NowPlayingController.shared().rac_valuesForKeyPath("nowPlayingItem", observer: self) +// let hasNowPlayingItemSignal = nowPlayingItemSignal.map({ $0 is PlaybackItem }) +// let isPlayingSignal = NowPlayingController.shared().rac_valuesForKeyPath("playing", observer: self) +// +// hasNowPlayingItemSignal.setKeyPath("shareButton.enabled", onObject: self) +// hasNowPlayingItemSignal.setKeyPath("playbackButton.enabled", onObject: self) +// +// navigationControl?.rac_liftSelector("setEnabled:forSegment:", withSignalsFromArray: [ +// rac_valuesForKeyPath("contentViewController.webView.canGoBack", observer: self), +// RACSignal.`return`(0) +// ]) +// +// navigationControl?.rac_liftSelector("setEnabled:forSegment:", withSignalsFromArray: [ +// rac_valuesForKeyPath("contentViewController.webView.canGoForward", observer: self), +// RACSignal.`return`(1) +// ]) +// +// nowPlayingItemSignal +// .map({ +// let item = $0 as? PlaybackItem +// return item?.prettyName() ?? "Cloudy: Nothing Playing" +// }) +// .setKeyPath("playbackButton.title", onObject: self) +// +// nowPlayingItemSignal +// .map({ +// let item = $0 as? PlaybackItem +// return item?.prettyName() ?? "Cloudy" +// }) +// .setKeyPath("window.title", onObject: self) +// +// let playbackButtonImageSignals = [ hasNowPlayingItemSignal, isPlayingSignal ] +// RACSignal.combineLatest(playbackButtonImageSignals) +// .map({ +// let tuple = $0 as! RACTuple +// let hasNowPlayingItem = tuple.first as! Bool +// let isPlaying = tuple.second as! Bool +// switch (hasNowPlayingItem, isPlaying) { +// case (true, true): +// return NSImage(named: "pause") +// case (true, false): +// return NSImage(named: "play") +// default: +// return nil +// } +// }) +// .setKeyPath("playbackButton.image", onObject: self) +// +// rac_liftSelector("webViewLoadingDidChange:", withSignalsFromArray: [ +// rac_valuesForKeyPath("contentViewController.webView.loading", observer: self) +// ]) } diff --git a/Cloudy/PlaybackViewController.swift b/Cloudy/PlaybackViewController.swift index 4208ddd..9346b70 100644 --- a/Cloudy/PlaybackViewController.swift +++ b/Cloudy/PlaybackViewController.swift @@ -10,162 +10,176 @@ import Cocoa import MediaKeys import WebKit -final class PlaybackViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler { +final class PlaybackViewController: NSViewController { - // MARK: - Properties - - private let mediaKeys = MediaKeys() - - private dynamic let webView: WKWebView = { - let script: WKUserScript = { - let url = NSBundle.mainBundle().URLForResource("cloudy", withExtension: "js")! - let contents = String(contentsOfURL: url, encoding: NSUTF8StringEncoding, error: nil)! - return WKUserScript(source: contents, injectionTime: WKUserScriptInjectionTime.AtDocumentEnd, forMainFrameOnly: true) - }() - - let configuration = WKWebViewConfiguration() - configuration.userContentController.addUserScript(script) - - #if DEBUG - configuration.preferences.setValue(true, forKey: "developerExtrasEnabled") - #endif - - let view = WKWebView(frame: CGRectZero, configuration: configuration) - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - - // MARK: - NSViewController - - override func viewDidLoad() { - super.viewDidLoad() - - mediaKeys.watch({ [unowned self] key in - switch key { - case .PlayPause: - self.togglePlaybackState(nil) - case .Forward: - self.seekForward() - case .Rewind: - self.seekBackward() - } - }) - - webView.configuration.userContentController.addScriptMessageHandler(self, name: "playbackHandler") - webView.configuration.userContentController.addScriptMessageHandler(self, name: "episodeHandler") - webView.configuration.userContentController.addScriptMessageHandler(self, name: "unplayedEpisodeCountHandler") - webView.navigationDelegate = self - - view.addSubview(webView) - setupConstraints() - - let url = NSURL(string: "https://overcast.fm") - let request = url.map({ NSURLRequest(URL: $0) }) - request.map({ webView.loadRequest($0) }) - } - - - // MARK: - Public - - func togglePlaybackState(sender: AnyObject?) { - webView.evaluateJavaScript("Cloudy.togglePlaybackState();", completionHandler: nil) - } - - - // MARK: - Private - - private func setupConstraints() { - let views = [ - "webView": webView - ] - - view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[webView]|", options: nil, metrics: nil, views: views)) - view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[webView]|", options: nil, metrics: nil, views: views)) - } - - private func handleUpdateEpisodeMessage(message: AnyObject?) { - let dictionary = message as? [String: AnyObject] - NowPlayingController.shared().nowPlayingItem = dictionary.map({ PlaybackItem(episodeDictionary: $0) }) - } - - private func handleUpdatePlaybackMessage(message: AnyObject?) { - NowPlayingController.shared().playing = message as? Bool ?? false - } - - private func handleUnplayedEpisodeCountMessage(message: AnyObject?) { - if let count = message as? Int { - NSApplication.sharedApplication().dockTile.badgeLabel = String(count) - } - } - - @objc private func performBrowserNavigation(sender: NSSegmentedControl) { - switch sender.selectedSegment { - case 0: - webView.goBack() - case 1: - webView.goForward() - default: - noop() - } - } - - @objc private func share(sender: NSButton) { - - // Build items - var items = [AnyObject]() - if let item = NowPlayingController.shared().nowPlayingItem?.prettyName() { - items.append(item) - } - if let item = webView.URL { - items.append(item) - } - if items.count == 0 { - return - } - - // Show picker - let picker = NSSharingServicePicker(items: items) - picker.showRelativeToRect(sender.bounds, ofView: sender, preferredEdge: NSMinYEdge) - } - - @objc private func reloadPage(sender: AnyObject?) { - webView.reload() - } - - private func seekBackward() { - webView.evaluateJavaScript("Cloudy.seekBackward();", completionHandler: nil) - } - - private func seekForward() { - webView.evaluateJavaScript("Cloudy.seekForward();", completionHandler: nil) - } - - - // MARK: - WKNavigationDelegate - - func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) { - if navigationAction.navigationType == .LinkActivated { - let host = navigationAction.request.URL?.host - let policy: WKNavigationActionPolicy = host == "overcast.fm" ? .Allow : .Cancel - decisionHandler(policy) - } - decisionHandler(.Allow) - } +} +extension PlaybackViewController: WKNavigationDelegate { - // MARK: - WKScriptMessageHandler +} +extension PlaybackViewController: WKScriptMessageHandler { func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { - switch message.name { - case "episodeHandler": - handleUpdateEpisodeMessage(message.body) - case "playbackHandler": - handleUpdatePlaybackMessage(message.body) - case "unplayedEpisodeCountHandler": - handleUnplayedEpisodeCountMessage(message.body) - default: - noop() - } + } } + +//final class PlaybackViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler { +// +// // MARK: - Properties +// +// private let mediaKeys = MediaKeys() +// +// private dynamic let webView: WKWebView = { +// let script: WKUserScript = { +// let url = NSBundle.mainBundle().URLForResource("cloudy", withExtension: "js")! +// let contents = String(contentsOfURL: url, encoding: NSUTF8StringEncoding, error: nil)! +// return WKUserScript(source: contents, injectionTime: WKUserScriptInjectionTime.AtDocumentEnd, forMainFrameOnly: true) +// }() +// +// let configuration = WKWebViewConfiguration() +// configuration.userContentController.addUserScript(script) +// +// #if DEBUG +// configuration.preferences.setValue(true, forKey: "developerExtrasEnabled") +// #endif +// +// let view = WKWebView(frame: CGRectZero, configuration: configuration) +// view.translatesAutoresizingMaskIntoConstraints = false +// return view +// }() +// +// +// // MARK: - NSViewController +// +// override func viewDidLoad() { +// super.viewDidLoad() +// +// mediaKeys.watch({ [unowned self] key in +// switch key { +// case .PlayPause: +// self.togglePlaybackState(nil) +// case .Forward: +// self.seekForward() +// case .Rewind: +// self.seekBackward() +// } +// }) +// +// webView.configuration.userContentController.addScriptMessageHandler(self, name: "playbackHandler") +// webView.configuration.userContentController.addScriptMessageHandler(self, name: "episodeHandler") +// webView.configuration.userContentController.addScriptMessageHandler(self, name: "unplayedEpisodeCountHandler") +// webView.navigationDelegate = self +// +// view.addSubview(webView) +// setupConstraints() +// +// let url = NSURL(string: "https://overcast.fm") +// let request = url.map({ NSURLRequest(URL: $0) }) +// request.map({ webView.loadRequest($0) }) +// } +// +// +// // MARK: - Public +// +// func togglePlaybackState(sender: AnyObject?) { +// webView.evaluateJavaScript("Cloudy.togglePlaybackState();", completionHandler: nil) +// } +// +// +// // MARK: - Private +// +// private func setupConstraints() { +// let views = [ +// "webView": webView +// ] +// +// view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[webView]|", options: nil, metrics: nil, views: views)) +// view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[webView]|", options: nil, metrics: nil, views: views)) +// } +// +// private func handleUpdateEpisodeMessage(message: AnyObject?) { +// let dictionary = message as? [String: AnyObject] +// NowPlayingController.shared().nowPlayingItem = dictionary.map({ PlaybackItem(episodeDictionary: $0) }) +// } +// +// private func handleUpdatePlaybackMessage(message: AnyObject?) { +// NowPlayingController.shared().playing = message as? Bool ?? false +// } +// +// private func handleUnplayedEpisodeCountMessage(message: AnyObject?) { +// if let count = message as? Int { +// NSApplication.sharedApplication().dockTile.badgeLabel = String(count) +// } +// } +// +// @objc private func performBrowserNavigation(sender: NSSegmentedControl) { +// switch sender.selectedSegment { +// case 0: +// webView.goBack() +// case 1: +// webView.goForward() +// default: +// noop() +// } +// } +// +// @objc private func share(sender: NSButton) { +// +// // Build items +// var items = [AnyObject]() +// if let item = NowPlayingController.shared().nowPlayingItem?.prettyName() { +// items.append(item) +// } +// if let item = webView.URL { +// items.append(item) +// } +// if items.count == 0 { +// return +// } +// +// // Show picker +// let picker = NSSharingServicePicker(items: items) +// picker.showRelativeToRect(sender.bounds, ofView: sender, preferredEdge: NSMinYEdge) +// } +// +// @objc private func reloadPage(sender: AnyObject?) { +// webView.reload() +// } +// +// private func seekBackward() { +// webView.evaluateJavaScript("Cloudy.seekBackward();", completionHandler: nil) +// } +// +// private func seekForward() { +// webView.evaluateJavaScript("Cloudy.seekForward();", completionHandler: nil) +// } +// +// +// // MARK: - WKNavigationDelegate +// +// func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) { +// if navigationAction.navigationType == .LinkActivated { +// let host = navigationAction.request.URL?.host +// let policy: WKNavigationActionPolicy = host == "overcast.fm" ? .Allow : .Cancel +// decisionHandler(policy) +// } +// decisionHandler(.Allow) +// } +// +// +// // MARK: - WKScriptMessageHandler +// +// func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { +// switch message.name { +// case "episodeHandler": +// handleUpdateEpisodeMessage(message.body) +// case "playbackHandler": +// handleUpdatePlaybackMessage(message.body) +// case "unplayedEpisodeCountHandler": +// handleUnplayedEpisodeCountMessage(message.body) +// default: +// noop() +// } +// } +//} From f985f7f9c2e6adfc5c501f9d2a9e8348fa492831 Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Tue, 12 Jan 2016 00:02:08 -0800 Subject: [PATCH 04/10] Restore some web view stuff. --- Cloudy/PlaybackViewController.swift | 197 +++++++++++++--------------- 1 file changed, 93 insertions(+), 104 deletions(-) diff --git a/Cloudy/PlaybackViewController.swift b/Cloudy/PlaybackViewController.swift index 9346b70..e5907d8 100644 --- a/Cloudy/PlaybackViewController.swift +++ b/Cloudy/PlaybackViewController.swift @@ -12,15 +12,106 @@ import WebKit final class PlaybackViewController: NSViewController { + // MARK: - Properties + + private lazy var webView: WKWebView = { + let script: WKUserScript = { + let URL = NSBundle.mainBundle().URLForResource("cloudy", withExtension: "js")! + let contents = try! String(contentsOfURL: URL, encoding: NSUTF8StringEncoding) + return WKUserScript(source: contents, injectionTime: WKUserScriptInjectionTime.AtDocumentEnd, forMainFrameOnly: true) + }() + + let configuration = WKWebViewConfiguration() + configuration.userContentController.addUserScript(script) + + #if DEBUG + configuration.preferences.setValue(true, forKey: "developerExtrasEnabled") + #endif + + let view = WKWebView(frame: .zero, configuration: configuration) + view.autoresizingMask = [ .ViewWidthSizable, .ViewHeightSizable ] + view.configuration.userContentController.addScriptMessageHandler(self, name: "playbackHandler") + view.configuration.userContentController.addScriptMessageHandler(self, name: "episodeHandler") + view.configuration.userContentController.addScriptMessageHandler(self, name: "unplayedEpisodeCountHandler") + view.navigationDelegate = self + return view + }() + + + // MARK: - NSViewController + + override func viewDidLoad() { + super.viewDidLoad() + + webView.frame = view.bounds + view.addSubview(webView) + + let URL = NSURL(string: "https://overcast.fm")! + let request = NSURLRequest(URL: URL) + webView.loadRequest(request) + } + + + // MARK: - Private + + @objc private func navigate(sender: NSSegmentedControl) { + switch sender.selectedSegment { + case 0: + webView.goBack() + case 1: + webView.goForward() + default: + () + } + } + + @objc private func share(sender: NSButton) { + + // Build items + var items = [AnyObject]() + if let item = NowPlayingController.shared().nowPlayingItem?.prettyName() { + items.append(item) + } + if let item = webView.URL { + items.append(item) + } + if items.count == 0 { + return + } + + // Show picker + let picker = NSSharingServicePicker(items: items) + picker.showRelativeToRect(sender.bounds, ofView: sender, preferredEdge: .MinY) + } + + @objc private func reload() { + webView.reload() + } } extension PlaybackViewController: WKNavigationDelegate { - + func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) { + if navigationAction.navigationType == .LinkActivated { + let host = navigationAction.request.URL?.host + let policy: WKNavigationActionPolicy = host == "overcast.fm" ? .Allow : .Cancel + decisionHandler(policy) + } + decisionHandler(.Allow) + } } extension PlaybackViewController: WKScriptMessageHandler { func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { - + switch message.name { + case "episodeHandler": + () + case "playbackHandler": + () + case "unplayedEpisodeCountHandler": + () + default: + noop() + } } } @@ -30,25 +121,6 @@ extension PlaybackViewController: WKScriptMessageHandler { // // private let mediaKeys = MediaKeys() // -// private dynamic let webView: WKWebView = { -// let script: WKUserScript = { -// let url = NSBundle.mainBundle().URLForResource("cloudy", withExtension: "js")! -// let contents = String(contentsOfURL: url, encoding: NSUTF8StringEncoding, error: nil)! -// return WKUserScript(source: contents, injectionTime: WKUserScriptInjectionTime.AtDocumentEnd, forMainFrameOnly: true) -// }() -// -// let configuration = WKWebViewConfiguration() -// configuration.userContentController.addUserScript(script) -// -// #if DEBUG -// configuration.preferences.setValue(true, forKey: "developerExtrasEnabled") -// #endif -// -// let view = WKWebView(frame: CGRectZero, configuration: configuration) -// view.translatesAutoresizingMaskIntoConstraints = false -// return view -// }() -// // // // MARK: - NSViewController // @@ -65,18 +137,6 @@ extension PlaybackViewController: WKScriptMessageHandler { // self.seekBackward() // } // }) -// -// webView.configuration.userContentController.addScriptMessageHandler(self, name: "playbackHandler") -// webView.configuration.userContentController.addScriptMessageHandler(self, name: "episodeHandler") -// webView.configuration.userContentController.addScriptMessageHandler(self, name: "unplayedEpisodeCountHandler") -// webView.navigationDelegate = self -// -// view.addSubview(webView) -// setupConstraints() -// -// let url = NSURL(string: "https://overcast.fm") -// let request = url.map({ NSURLRequest(URL: $0) }) -// request.map({ webView.loadRequest($0) }) // } // // @@ -89,15 +149,6 @@ extension PlaybackViewController: WKScriptMessageHandler { // // // MARK: - Private // -// private func setupConstraints() { -// let views = [ -// "webView": webView -// ] -// -// view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[webView]|", options: nil, metrics: nil, views: views)) -// view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[webView]|", options: nil, metrics: nil, views: views)) -// } -// // private func handleUpdateEpisodeMessage(message: AnyObject?) { // let dictionary = message as? [String: AnyObject] // NowPlayingController.shared().nowPlayingItem = dictionary.map({ PlaybackItem(episodeDictionary: $0) }) @@ -113,40 +164,6 @@ extension PlaybackViewController: WKScriptMessageHandler { // } // } // -// @objc private func performBrowserNavigation(sender: NSSegmentedControl) { -// switch sender.selectedSegment { -// case 0: -// webView.goBack() -// case 1: -// webView.goForward() -// default: -// noop() -// } -// } -// -// @objc private func share(sender: NSButton) { -// -// // Build items -// var items = [AnyObject]() -// if let item = NowPlayingController.shared().nowPlayingItem?.prettyName() { -// items.append(item) -// } -// if let item = webView.URL { -// items.append(item) -// } -// if items.count == 0 { -// return -// } -// -// // Show picker -// let picker = NSSharingServicePicker(items: items) -// picker.showRelativeToRect(sender.bounds, ofView: sender, preferredEdge: NSMinYEdge) -// } -// -// @objc private func reloadPage(sender: AnyObject?) { -// webView.reload() -// } -// // private func seekBackward() { // webView.evaluateJavaScript("Cloudy.seekBackward();", completionHandler: nil) // } @@ -154,32 +171,4 @@ extension PlaybackViewController: WKScriptMessageHandler { // private func seekForward() { // webView.evaluateJavaScript("Cloudy.seekForward();", completionHandler: nil) // } -// -// -// // MARK: - WKNavigationDelegate -// -// func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) { -// if navigationAction.navigationType == .LinkActivated { -// let host = navigationAction.request.URL?.host -// let policy: WKNavigationActionPolicy = host == "overcast.fm" ? .Allow : .Cancel -// decisionHandler(policy) -// } -// decisionHandler(.Allow) -// } -// -// -// // MARK: - WKScriptMessageHandler -// -// func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { -// switch message.name { -// case "episodeHandler": -// handleUpdateEpisodeMessage(message.body) -// case "playbackHandler": -// handleUpdatePlaybackMessage(message.body) -// case "unplayedEpisodeCountHandler": -// handleUnplayedEpisodeCountMessage(message.body) -// default: -// noop() -// } -// } //} From b6f050852afcf42e5c8e8ba902cc666567461789 Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Tue, 12 Jan 2016 00:03:43 -0800 Subject: [PATCH 05/10] Better window behavior. --- Cloudy/Base.lproj/Main.storyboard | 2 +- Cloudy/MainWindowController.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cloudy/Base.lproj/Main.storyboard b/Cloudy/Base.lproj/Main.storyboard index b8bdac3..381493e 100644 --- a/Cloudy/Base.lproj/Main.storyboard +++ b/Cloudy/Base.lproj/Main.storyboard @@ -664,7 +664,7 @@ - + diff --git a/Cloudy/MainWindowController.swift b/Cloudy/MainWindowController.swift index 163c8b0..e7e4c86 100644 --- a/Cloudy/MainWindowController.swift +++ b/Cloudy/MainWindowController.swift @@ -27,10 +27,10 @@ final class MainWindowController: NSWindowController, NSWindowDelegate { override func windowDidLoad() { super.windowDidLoad() -// window?.titleVisibility = .Hidden -// window?.excludedFromWindowsMenu = true -// shareButton?.sendActionOn(Int(NSEventMask.LeftMouseDownMask.rawValue)) -// playbackButton?.imagePosition = .ImageLeft + window?.titleVisibility = .Hidden + window?.excludedFromWindowsMenu = true + shareButton?.sendActionOn(Int(NSEventMask.LeftMouseDownMask.rawValue)) + playbackButton?.imagePosition = .ImageLeft // // let nowPlayingItemSignal = NowPlayingController.shared().rac_valuesForKeyPath("nowPlayingItem", observer: self) // let hasNowPlayingItemSignal = nowPlayingItemSignal.map({ $0 is PlaybackItem }) From ce218f9a4a712ec57a4a2b1452e863fed9be9936 Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Tue, 12 Jan 2016 00:35:10 -0800 Subject: [PATCH 06/10] Make some stuff work. --- Cloudy/MainWindowController.swift | 73 +++++++++++++++++------------ Cloudy/PlaybackViewController.swift | 71 +++++++++++++++++++--------- 2 files changed, 92 insertions(+), 52 deletions(-) diff --git a/Cloudy/MainWindowController.swift b/Cloudy/MainWindowController.swift index e7e4c86..3769f6a 100644 --- a/Cloudy/MainWindowController.swift +++ b/Cloudy/MainWindowController.swift @@ -15,9 +15,9 @@ final class MainWindowController: NSWindowController, NSWindowDelegate { @IBOutlet var navigationControl: NSSegmentedControl? - @IBOutlet var shareButton: NSButton? + @IBOutlet private var shareButton: NSButton! - @IBOutlet var playbackButton: NSButton? + @IBOutlet private var playbackButton: NSButton! @IBOutlet var loadingIndicator: NSProgressIndicator? @@ -29,16 +29,51 @@ final class MainWindowController: NSWindowController, NSWindowDelegate { window?.titleVisibility = .Hidden window?.excludedFromWindowsMenu = true - shareButton?.sendActionOn(Int(NSEventMask.LeftMouseDownMask.rawValue)) - playbackButton?.imagePosition = .ImageLeft + shareButton.sendActionOn(Int(NSEventMask.LeftMouseDownMask.rawValue)) + playbackButton.imagePosition = .ImageLeft + + let playbackController = contentViewController as! PlaybackViewController + + playbackController.nowPlayingItem.producer + .map({ item -> String in + switch item { + case let item?: + return "\(item.showName): \(item.episodeName)" + case .None: + return "Cloudy: Nothing Playing" + } + }) + .startWithNext({ [weak self] thing in + self?.playbackButton.title = thing + }) + + playbackController.nowPlayingItem.producer + .combineLatestWith(playbackController.isPlaying.producer) + .map({ nowPlayingItem, isPlaying -> NSImage? in + switch (nowPlayingItem, isPlaying) { + case (.Some, true): + return NSImage(named: "pause") + case (.Some, false): + return NSImage(named: "play") + default: + return nil + } + }) + .startWithNext({ [weak self] image in + self?.playbackButton.image = image + }) + + playbackController.nowPlayingItem.producer + .map({ $0 != nil }) + .startWithNext({ [weak self] enabled in + self?.shareButton.enabled = enabled + self?.playbackButton.enabled = enabled + }) // // let nowPlayingItemSignal = NowPlayingController.shared().rac_valuesForKeyPath("nowPlayingItem", observer: self) // let hasNowPlayingItemSignal = nowPlayingItemSignal.map({ $0 is PlaybackItem }) // let isPlayingSignal = NowPlayingController.shared().rac_valuesForKeyPath("playing", observer: self) // -// hasNowPlayingItemSignal.setKeyPath("shareButton.enabled", onObject: self) -// hasNowPlayingItemSignal.setKeyPath("playbackButton.enabled", onObject: self) -// // navigationControl?.rac_liftSelector("setEnabled:forSegment:", withSignalsFromArray: [ // rac_valuesForKeyPath("contentViewController.webView.canGoBack", observer: self), // RACSignal.`return`(0) @@ -52,34 +87,10 @@ final class MainWindowController: NSWindowController, NSWindowDelegate { // nowPlayingItemSignal // .map({ // let item = $0 as? PlaybackItem -// return item?.prettyName() ?? "Cloudy: Nothing Playing" -// }) -// .setKeyPath("playbackButton.title", onObject: self) -// -// nowPlayingItemSignal -// .map({ -// let item = $0 as? PlaybackItem // return item?.prettyName() ?? "Cloudy" // }) // .setKeyPath("window.title", onObject: self) // -// let playbackButtonImageSignals = [ hasNowPlayingItemSignal, isPlayingSignal ] -// RACSignal.combineLatest(playbackButtonImageSignals) -// .map({ -// let tuple = $0 as! RACTuple -// let hasNowPlayingItem = tuple.first as! Bool -// let isPlaying = tuple.second as! Bool -// switch (hasNowPlayingItem, isPlaying) { -// case (true, true): -// return NSImage(named: "pause") -// case (true, false): -// return NSImage(named: "play") -// default: -// return nil -// } -// }) -// .setKeyPath("playbackButton.image", onObject: self) -// // rac_liftSelector("webViewLoadingDidChange:", withSignalsFromArray: [ // rac_valuesForKeyPath("contentViewController.webView.loading", observer: self) // ]) diff --git a/Cloudy/PlaybackViewController.swift b/Cloudy/PlaybackViewController.swift index e5907d8..82757cc 100644 --- a/Cloudy/PlaybackViewController.swift +++ b/Cloudy/PlaybackViewController.swift @@ -9,6 +9,7 @@ import Cocoa import MediaKeys import WebKit +import ReactiveCocoa final class PlaybackViewController: NSViewController { @@ -23,6 +24,9 @@ final class PlaybackViewController: NSViewController { let configuration = WKWebViewConfiguration() configuration.userContentController.addUserScript(script) + configuration.userContentController.addScriptMessageHandler(self, name: "playbackHandler") + configuration.userContentController.addScriptMessageHandler(self, name: "episodeHandler") + configuration.userContentController.addScriptMessageHandler(self, name: "unplayedEpisodeCountHandler") #if DEBUG configuration.preferences.setValue(true, forKey: "developerExtrasEnabled") @@ -30,13 +34,28 @@ final class PlaybackViewController: NSViewController { let view = WKWebView(frame: .zero, configuration: configuration) view.autoresizingMask = [ .ViewWidthSizable, .ViewHeightSizable ] - view.configuration.userContentController.addScriptMessageHandler(self, name: "playbackHandler") - view.configuration.userContentController.addScriptMessageHandler(self, name: "episodeHandler") - view.configuration.userContentController.addScriptMessageHandler(self, name: "unplayedEpisodeCountHandler") view.navigationDelegate = self return view }() + private let _nowPlayingItem = MutableProperty(nil) + + private let _unplayedEpisodeCount = MutableProperty(nil) + + private let _isPlaying = MutableProperty(false) + + var nowPlayingItem: AnyProperty { + return AnyProperty(_nowPlayingItem) + } + + var unplayedEpisodeCount: AnyProperty { + return AnyProperty(_unplayedEpisodeCount) + } + + var isPlaying: AnyProperty { + return AnyProperty(_isPlaying) + } + // MARK: - NSViewController @@ -104,13 +123,37 @@ extension PlaybackViewController: WKScriptMessageHandler { func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { switch message.name { case "episodeHandler": - () + let dictionary = message.body as? [String: String] + _nowPlayingItem.value = dictionary.flatMap(MediaItem.init) case "playbackHandler": - () + if let playing = message.body as? Bool { + _isPlaying.value = playing + } case "unplayedEpisodeCountHandler": - () + if let count = message.body as? Int { + _unplayedEpisodeCount.value = count + } default: - noop() + () + } + } +} + +extension PlaybackViewController { + struct MediaItem { + var showName: String + var episodeName: String + + init?(dictionary: [String: String]) { + guard + let showName = dictionary["show_title"], + let episodeName = dictionary["episode_title"] + else { + return nil + } + + self.showName = showName + self.episodeName = episodeName } } } @@ -149,20 +192,6 @@ extension PlaybackViewController: WKScriptMessageHandler { // // // MARK: - Private // -// private func handleUpdateEpisodeMessage(message: AnyObject?) { -// let dictionary = message as? [String: AnyObject] -// NowPlayingController.shared().nowPlayingItem = dictionary.map({ PlaybackItem(episodeDictionary: $0) }) -// } -// -// private func handleUpdatePlaybackMessage(message: AnyObject?) { -// NowPlayingController.shared().playing = message as? Bool ?? false -// } -// -// private func handleUnplayedEpisodeCountMessage(message: AnyObject?) { -// if let count = message as? Int { -// NSApplication.sharedApplication().dockTile.badgeLabel = String(count) -// } -// } // // private func seekBackward() { // webView.evaluateJavaScript("Cloudy.seekBackward();", completionHandler: nil) From 791524586ad7e3572d3d30b82557baa1db193765 Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Tue, 12 Jan 2016 00:38:26 -0800 Subject: [PATCH 07/10] Remove some unused stuff. --- Cloudy.xcodeproj/project.pbxproj | 8 ------ Cloudy/Defines.swift | 44 ----------------------------- Cloudy/MainWindowController.swift | 2 +- Cloudy/NowPlayingController.swift | 27 ------------------ Cloudy/PlaybackViewController.swift | 14 +++++---- 5 files changed, 10 insertions(+), 85 deletions(-) delete mode 100644 Cloudy/Defines.swift delete mode 100644 Cloudy/NowPlayingController.swift diff --git a/Cloudy.xcodeproj/project.pbxproj b/Cloudy.xcodeproj/project.pbxproj index 61e044a..5a14f58 100644 --- a/Cloudy.xcodeproj/project.pbxproj +++ b/Cloudy.xcodeproj/project.pbxproj @@ -8,14 +8,12 @@ /* Begin PBXBuildFile section */ 3AE739B31C44E7350004A7A5 /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE739B11C44E5F40004A7A5 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 3B0206EC1AE7461400E45D53 /* NowPlayingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B0206EB1AE7461400E45D53 /* NowPlayingController.swift */; }; 3B1A19201B4E681A005CE283 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BA5FE361AF35A8E00A5622B /* Sparkle.framework */; }; 3B430E261AE0A88F00AEBFBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B430E251AE0A88F00AEBFBC /* AppDelegate.swift */; }; 3B430E281AE0A88F00AEBFBC /* PlaybackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B430E271AE0A88F00AEBFBC /* PlaybackViewController.swift */; }; 3B430E2A1AE0A88F00AEBFBC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3B430E291AE0A88F00AEBFBC /* Images.xcassets */; }; 3B430E2D1AE0A88F00AEBFBC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3B430E2B1AE0A88F00AEBFBC /* Main.storyboard */; }; 3B430E441AE0B20700AEBFBC /* cloudy.js in Resources */ = {isa = PBXBuildFile; fileRef = 3B430E431AE0B20700AEBFBC /* cloudy.js */; }; - 3B430E461AE0C0FE00AEBFBC /* Defines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B430E451AE0C0FE00AEBFBC /* Defines.swift */; }; 3B430E4A1AE0C66200AEBFBC /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B430E491AE0C66200AEBFBC /* MainWindowController.swift */; }; 3BA5FE391AF35A9800A5622B /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3BA5FE361AF35A8E00A5622B /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3BB0D1B91AE2263100AB4A33 /* ReactiveCocoa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2332D41AE1BA48005C6654 /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -42,7 +40,6 @@ /* Begin PBXFileReference section */ 3AE739B11C44E5F40004A7A5 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/Mac/Result.framework; sourceTree = ""; }; - 3B0206EB1AE7461400E45D53 /* NowPlayingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NowPlayingController.swift; sourceTree = ""; }; 3B2332D41AE1BA48005C6654 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveCocoa.framework; path = Carthage/Build/Mac/ReactiveCocoa.framework; sourceTree = ""; }; 3B430E201AE0A88F00AEBFBC /* Cloudy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cloudy.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3B430E241AE0A88F00AEBFBC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -51,7 +48,6 @@ 3B430E291AE0A88F00AEBFBC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 3B430E2C1AE0A88F00AEBFBC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 3B430E431AE0B20700AEBFBC /* cloudy.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cloudy.js; sourceTree = ""; }; - 3B430E451AE0C0FE00AEBFBC /* Defines.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Defines.swift; sourceTree = ""; }; 3B430E491AE0C66200AEBFBC /* MainWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = ""; }; 3BA5FE361AF35A8E00A5622B /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Carthage/Build/Mac/Sparkle.framework; sourceTree = ""; }; 3BFB22291AFA070C00127AF1 /* MediaKeys.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaKeys.framework; path = Carthage/Build/Mac/MediaKeys.framework; sourceTree = ""; }; @@ -104,8 +100,6 @@ 3B430E251AE0A88F00AEBFBC /* AppDelegate.swift */, 3B430E491AE0C66200AEBFBC /* MainWindowController.swift */, 3B430E271AE0A88F00AEBFBC /* PlaybackViewController.swift */, - 3B0206EB1AE7461400E45D53 /* NowPlayingController.swift */, - 3B430E451AE0C0FE00AEBFBC /* Defines.swift */, 3B430E421AE0A93000AEBFBC /* Resources */, ); path = Cloudy; @@ -202,11 +196,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3B0206EC1AE7461400E45D53 /* NowPlayingController.swift in Sources */, 3B430E281AE0A88F00AEBFBC /* PlaybackViewController.swift in Sources */, 3B430E261AE0A88F00AEBFBC /* AppDelegate.swift in Sources */, 3B430E4A1AE0C66200AEBFBC /* MainWindowController.swift in Sources */, - 3B430E461AE0C0FE00AEBFBC /* Defines.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Cloudy/Defines.swift b/Cloudy/Defines.swift deleted file mode 100644 index bec74c7..0000000 --- a/Cloudy/Defines.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Defines.swift -// Cloudy -// -// Created by Caleb Davenport on 4/16/15. -// Copyright (c) 2015 Caleb Davenport. All rights reserved. -// - -import Foundation - -func noop() {} - -class PlaybackItem: NSObject { - - // MARK: - Properties - - let show: String? - - let episode: String? - - - // MARK: - Initializers - - init(episodeDictionary dictionary: [String: AnyObject]) { - show = dictionary["show_title"] as? String - episode = dictionary["episode_title"] as? String - } - - - // MARK: - Helpers - - func prettyName() -> String? { - switch (show, episode) { - case (.Some(let show), .Some(let episode)): - return "\(show): \(episode)" - case (.Some(let show), .None): - return show - case (.None, .Some(let episode)): - return episode - default: - return nil - } - } -} diff --git a/Cloudy/MainWindowController.swift b/Cloudy/MainWindowController.swift index 3769f6a..6c5c3b4 100644 --- a/Cloudy/MainWindowController.swift +++ b/Cloudy/MainWindowController.swift @@ -38,7 +38,7 @@ final class MainWindowController: NSWindowController, NSWindowDelegate { .map({ item -> String in switch item { case let item?: - return "\(item.showName): \(item.episodeName)" + return item.compositeTitle case .None: return "Cloudy: Nothing Playing" } diff --git a/Cloudy/NowPlayingController.swift b/Cloudy/NowPlayingController.swift deleted file mode 100644 index 8d4c628..0000000 --- a/Cloudy/NowPlayingController.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// NowPlayingController.swift -// Cloudy -// -// Created by Caleb Davenport on 4/21/15. -// Copyright (c) 2015 Caleb Davenport. All rights reserved. -// - -import Foundation - -private let SharedController = NowPlayingController() - -class NowPlayingController: NSObject { - - // MARK: - Properties - - dynamic var playing = false - - dynamic var nowPlayingItem: PlaybackItem? - - - // MARK: - Public - - class func shared() -> NowPlayingController { - return SharedController - } -} diff --git a/Cloudy/PlaybackViewController.swift b/Cloudy/PlaybackViewController.swift index 82757cc..86cce76 100644 --- a/Cloudy/PlaybackViewController.swift +++ b/Cloudy/PlaybackViewController.swift @@ -88,7 +88,7 @@ final class PlaybackViewController: NSViewController { // Build items var items = [AnyObject]() - if let item = NowPlayingController.shared().nowPlayingItem?.prettyName() { + if let item = nowPlayingItem.value?.compositeTitle { items.append(item) } if let item = webView.URL { @@ -141,8 +141,12 @@ extension PlaybackViewController: WKScriptMessageHandler { extension PlaybackViewController { struct MediaItem { - var showName: String - var episodeName: String + var showTitle: String + var episodeTitle: String + + var compositeTitle: String { + return "\(showTitle): \(episodeTitle)" + } init?(dictionary: [String: String]) { guard @@ -152,8 +156,8 @@ extension PlaybackViewController { return nil } - self.showName = showName - self.episodeName = episodeName + self.showTitle = showName + self.episodeTitle = episodeName } } } From 584882fb1bfc18392353c5b1259f4de21025a0cb Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Tue, 12 Jan 2016 23:46:45 -0800 Subject: [PATCH 08/10] Make some more stuff work. --- Cloudy/Info.plist | 5 ++ Cloudy/MainWindowController.swift | 97 +++++++++++------------------ Cloudy/PlaybackViewController.swift | 51 ++++++++------- 3 files changed, 70 insertions(+), 83 deletions(-) diff --git a/Cloudy/Info.plist b/Cloudy/Info.plist index 81d3e1e..676c011 100644 --- a/Cloudy/Info.plist +++ b/Cloudy/Info.plist @@ -24,6 +24,11 @@ 5 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSHumanReadableCopyright Copyright © 2015 Caleb Davenport. All rights reserved. NSMainStoryboardFile diff --git a/Cloudy/MainWindowController.swift b/Cloudy/MainWindowController.swift index 6c5c3b4..e9dc0a0 100644 --- a/Cloudy/MainWindowController.swift +++ b/Cloudy/MainWindowController.swift @@ -9,17 +9,21 @@ import Cocoa import ReactiveCocoa -final class MainWindowController: NSWindowController, NSWindowDelegate { +final class MainWindowController: NSWindowController { // MARK: - Properties - @IBOutlet var navigationControl: NSSegmentedControl? + @IBOutlet private var navigationControl: NSSegmentedControl! @IBOutlet private var shareButton: NSButton! @IBOutlet private var playbackButton: NSButton! - @IBOutlet var loadingIndicator: NSProgressIndicator? + @IBOutlet private var loadingIndicator: NSProgressIndicator! + + private var playbackViewController: PlaybackViewController! { + return contentViewController as? PlaybackViewController + } // MARK: - NSWindowController @@ -32,23 +36,27 @@ final class MainWindowController: NSWindowController, NSWindowDelegate { shareButton.sendActionOn(Int(NSEventMask.LeftMouseDownMask.rawValue)) playbackButton.imagePosition = .ImageLeft - let playbackController = contentViewController as! PlaybackViewController + playbackViewController.nowPlayingItem.producer + .map({ $0?.compositeTitle ?? "Cloudy: Nothing Playing" }) + .startWithNext({ [weak self] in + self?.playbackButton.title = $0 + }) - playbackController.nowPlayingItem.producer - .map({ item -> String in - switch item { - case let item?: - return item.compositeTitle - case .None: - return "Cloudy: Nothing Playing" - } + playbackViewController.nowPlayingItem.producer + .map({ $0?.compositeTitle ?? "Cloudy" }) + .startWithNext({ [weak self] in + self?.window?.title = $0 }) - .startWithNext({ [weak self] thing in - self?.playbackButton.title = thing + + playbackViewController.nowPlayingItem.producer + .map({ $0 != nil }) + .startWithNext({ [weak self] enabled in + self?.shareButton.enabled = enabled + self?.playbackButton.enabled = enabled }) - playbackController.nowPlayingItem.producer - .combineLatestWith(playbackController.isPlaying.producer) + playbackViewController.nowPlayingItem.producer + .combineLatestWith(playbackViewController.isPlaying.producer) .map({ nowPlayingItem, isPlaying -> NSImage? in switch (nowPlayingItem, isPlaying) { case (.Some, true): @@ -63,54 +71,23 @@ final class MainWindowController: NSWindowController, NSWindowDelegate { self?.playbackButton.image = image }) - playbackController.nowPlayingItem.producer - .map({ $0 != nil }) - .startWithNext({ [weak self] enabled in - self?.shareButton.enabled = enabled - self?.playbackButton.enabled = enabled - }) -// -// let nowPlayingItemSignal = NowPlayingController.shared().rac_valuesForKeyPath("nowPlayingItem", observer: self) -// let hasNowPlayingItemSignal = nowPlayingItemSignal.map({ $0 is PlaybackItem }) -// let isPlayingSignal = NowPlayingController.shared().rac_valuesForKeyPath("playing", observer: self) -// -// navigationControl?.rac_liftSelector("setEnabled:forSegment:", withSignalsFromArray: [ -// rac_valuesForKeyPath("contentViewController.webView.canGoBack", observer: self), -// RACSignal.`return`(0) -// ]) -// -// navigationControl?.rac_liftSelector("setEnabled:forSegment:", withSignalsFromArray: [ -// rac_valuesForKeyPath("contentViewController.webView.canGoForward", observer: self), -// RACSignal.`return`(1) -// ]) -// -// nowPlayingItemSignal -// .map({ -// let item = $0 as? PlaybackItem -// return item?.prettyName() ?? "Cloudy" -// }) -// .setKeyPath("window.title", onObject: self) -// -// rac_liftSelector("webViewLoadingDidChange:", withSignalsFromArray: [ -// rac_valuesForKeyPath("contentViewController.webView.loading", observer: self) -// ]) - } - + playbackViewController.webViewCanGoBack.startWithNext({ [weak self] in + self?.navigationControl.setEnabled($0, forSegment: 0) + }) - // MARK: - Private + playbackViewController.webViewCanGoForward.startWithNext({ [weak self] in + self?.navigationControl.setEnabled($0, forSegment: 1) + }) - @objc private func webViewLoadingDidChange(loading: Bool) { - if loading { - loadingIndicator?.startAnimation(self) - } - else { - loadingIndicator?.stopAnimation(self) - } + playbackViewController.webViewLoading.startWithNext({ [weak self] in + guard let indicator = self?.loadingIndicator else { return } + if $0 { indicator.startAnimation(nil) } + else { indicator.stopAnimation(nil) } + }) } +} - - // MARK: - NSWindowDelegate - +extension MainWindowController: NSWindowDelegate { func windowShouldClose(sender: AnyObject) -> Bool { NSApplication.sharedApplication().terminate(self) return false diff --git a/Cloudy/PlaybackViewController.swift b/Cloudy/PlaybackViewController.swift index 86cce76..9d478d9 100644 --- a/Cloudy/PlaybackViewController.swift +++ b/Cloudy/PlaybackViewController.swift @@ -40,7 +40,7 @@ final class PlaybackViewController: NSViewController { private let _nowPlayingItem = MutableProperty(nil) - private let _unplayedEpisodeCount = MutableProperty(nil) + private let _unplayedEpisodeCount = MutableProperty(0) private let _isPlaying = MutableProperty(false) @@ -48,7 +48,7 @@ final class PlaybackViewController: NSViewController { return AnyProperty(_nowPlayingItem) } - var unplayedEpisodeCount: AnyProperty { + var unplayedEpisodeCount: AnyProperty { return AnyProperty(_unplayedEpisodeCount) } @@ -56,6 +56,18 @@ final class PlaybackViewController: NSViewController { return AnyProperty(_isPlaying) } + var webViewCanGoBack: SignalProducer { + return DynamicProperty(object: webView, keyPath: "canGoBack").producer.map({ $0 as? Bool }).ignoreNil() + } + + var webViewCanGoForward: SignalProducer { + return DynamicProperty(object: webView, keyPath: "canGoForward").producer.map({ $0 as? Bool }).ignoreNil() + } + + var webViewLoading: SignalProducer { + return DynamicProperty(object: webView, keyPath: "loading").producer.map({ $0 as? Bool }).ignoreNil() + } + // MARK: - NSViewController @@ -103,9 +115,13 @@ final class PlaybackViewController: NSViewController { picker.showRelativeToRect(sender.bounds, ofView: sender, preferredEdge: .MinY) } - @objc private func reload() { + @objc private func reload(_: AnyObject?) { webView.reload() } + + @objc private func togglePlaybackState(_: AnyObject?) { + webView.evaluateJavaScript("Cloudy.togglePlaybackState();", completionHandler: nil) + } } extension PlaybackViewController: WKNavigationDelegate { @@ -121,18 +137,15 @@ extension PlaybackViewController: WKNavigationDelegate { extension PlaybackViewController: WKScriptMessageHandler { func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { - switch message.name { - case "episodeHandler": - let dictionary = message.body as? [String: String] - _nowPlayingItem.value = dictionary.flatMap(MediaItem.init) - case "playbackHandler": - if let playing = message.body as? Bool { - _isPlaying.value = playing - } - case "unplayedEpisodeCountHandler": - if let count = message.body as? Int { - _unplayedEpisodeCount.value = count - } + switch (message.name, message.body) { + case ("episodeHandler", let body as [String: String]): + _nowPlayingItem.value = MediaItem(dictionary: body) + case ("episodeHandler", _): + _nowPlayingItem.value = nil + case ("playbackHandler", let body as Bool): + _isPlaying.value = body + case ("unplayedEpisodeCountHandler", let body as Int): + _unplayedEpisodeCount.value = body default: () } @@ -187,16 +200,8 @@ extension PlaybackViewController { // } // // -// // MARK: - Public -// -// func togglePlaybackState(sender: AnyObject?) { -// webView.evaluateJavaScript("Cloudy.togglePlaybackState();", completionHandler: nil) -// } -// -// // // MARK: - Private // -// // private func seekBackward() { // webView.evaluateJavaScript("Cloudy.seekBackward();", completionHandler: nil) // } From d65b9c115fd58a9c4b99e7411e7feeec796d9d0a Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Wed, 13 Jan 2016 00:03:13 -0800 Subject: [PATCH 09/10] Clean up some things. --- Cloudy.xcodeproj/project.pbxproj | 4 +++ Cloudy/Cloudy.swift | 24 +++++++++++++++++ Cloudy/PlaybackViewController.swift | 41 +++++++++-------------------- 3 files changed, 40 insertions(+), 29 deletions(-) create mode 100644 Cloudy/Cloudy.swift diff --git a/Cloudy.xcodeproj/project.pbxproj b/Cloudy.xcodeproj/project.pbxproj index 5a14f58..c8c460f 100644 --- a/Cloudy.xcodeproj/project.pbxproj +++ b/Cloudy.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 3AE739B31C44E7350004A7A5 /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE739B11C44E5F40004A7A5 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3AF748D01C46394700216897 /* Cloudy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF748CF1C46394700216897 /* Cloudy.swift */; }; 3B1A19201B4E681A005CE283 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BA5FE361AF35A8E00A5622B /* Sparkle.framework */; }; 3B430E261AE0A88F00AEBFBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B430E251AE0A88F00AEBFBC /* AppDelegate.swift */; }; 3B430E281AE0A88F00AEBFBC /* PlaybackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B430E271AE0A88F00AEBFBC /* PlaybackViewController.swift */; }; @@ -40,6 +41,7 @@ /* Begin PBXFileReference section */ 3AE739B11C44E5F40004A7A5 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/Mac/Result.framework; sourceTree = ""; }; + 3AF748CF1C46394700216897 /* Cloudy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cloudy.swift; sourceTree = ""; }; 3B2332D41AE1BA48005C6654 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveCocoa.framework; path = Carthage/Build/Mac/ReactiveCocoa.framework; sourceTree = ""; }; 3B430E201AE0A88F00AEBFBC /* Cloudy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cloudy.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3B430E241AE0A88F00AEBFBC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -100,6 +102,7 @@ 3B430E251AE0A88F00AEBFBC /* AppDelegate.swift */, 3B430E491AE0C66200AEBFBC /* MainWindowController.swift */, 3B430E271AE0A88F00AEBFBC /* PlaybackViewController.swift */, + 3AF748CF1C46394700216897 /* Cloudy.swift */, 3B430E421AE0A93000AEBFBC /* Resources */, ); path = Cloudy; @@ -199,6 +202,7 @@ 3B430E281AE0A88F00AEBFBC /* PlaybackViewController.swift in Sources */, 3B430E261AE0A88F00AEBFBC /* AppDelegate.swift in Sources */, 3B430E4A1AE0C66200AEBFBC /* MainWindowController.swift in Sources */, + 3AF748D01C46394700216897 /* Cloudy.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Cloudy/Cloudy.swift b/Cloudy/Cloudy.swift new file mode 100644 index 0000000..cb9622f --- /dev/null +++ b/Cloudy/Cloudy.swift @@ -0,0 +1,24 @@ +// +// Cloudy.swift +// Cloudy +// +// Created by Caleb Davenport on 1/12/16. +// Copyright © 2016 Caleb Davenport. All rights reserved. +// + +import WebKit +import ReactiveCocoa + +extension WKWebView { + final var canGoBackProducer: SignalProducer { + return DynamicProperty(object: self, keyPath: "canGoBack").producer.map({ $0 as! Bool }) + } + + final var canGoForwardProducer: SignalProducer { + return DynamicProperty(object: self, keyPath: "canGoForward").producer.map({ $0 as! Bool }) + } + + final var loadingProducer: SignalProducer { + return DynamicProperty(object: self, keyPath: "loading").producer.map({ $0 as! Bool }) + } +} diff --git a/Cloudy/PlaybackViewController.swift b/Cloudy/PlaybackViewController.swift index 9d478d9..9e36b4d 100644 --- a/Cloudy/PlaybackViewController.swift +++ b/Cloudy/PlaybackViewController.swift @@ -44,29 +44,17 @@ final class PlaybackViewController: NSViewController { private let _isPlaying = MutableProperty(false) - var nowPlayingItem: AnyProperty { - return AnyProperty(_nowPlayingItem) - } + var nowPlayingItem: AnyProperty { return AnyProperty(_nowPlayingItem) } - var unplayedEpisodeCount: AnyProperty { - return AnyProperty(_unplayedEpisodeCount) - } + var unplayedEpisodeCount: AnyProperty { return AnyProperty(_unplayedEpisodeCount) } - var isPlaying: AnyProperty { - return AnyProperty(_isPlaying) - } + var isPlaying: AnyProperty { return AnyProperty(_isPlaying) } - var webViewCanGoBack: SignalProducer { - return DynamicProperty(object: webView, keyPath: "canGoBack").producer.map({ $0 as? Bool }).ignoreNil() - } + var webViewCanGoBack: SignalProducer { return webView.canGoBackProducer } - var webViewCanGoForward: SignalProducer { - return DynamicProperty(object: webView, keyPath: "canGoForward").producer.map({ $0 as? Bool }).ignoreNil() - } + var webViewCanGoForward: SignalProducer { return webView.canGoForwardProducer } - var webViewLoading: SignalProducer { - return DynamicProperty(object: webView, keyPath: "loading").producer.map({ $0 as? Bool }).ignoreNil() - } + var webViewLoading: SignalProducer { return webView.loadingProducer } // MARK: - NSViewController @@ -97,20 +85,15 @@ final class PlaybackViewController: NSViewController { } @objc private func share(sender: NSButton) { - - // Build items - var items = [AnyObject]() - if let item = nowPlayingItem.value?.compositeTitle { - items.append(item) - } - if let item = webView.URL { - items.append(item) - } - if items.count == 0 { + guard + let text = nowPlayingItem.value?.compositeTitle, + let URL = webView.URL + else { return } - // Show picker + let items = [ text, URL ] + let picker = NSSharingServicePicker(items: items) picker.showRelativeToRect(sender.bounds, ofView: sender, preferredEdge: .MinY) } From 71808b62ff44039dd3c147305b21d3a0ef55d328 Mon Sep 17 00:00:00 2001 From: Caleb Davenport Date: Wed, 13 Jan 2016 00:05:52 -0800 Subject: [PATCH 10/10] Change window presentation style. --- Cloudy/Base.lproj/Main.storyboard | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cloudy/Base.lproj/Main.storyboard b/Cloudy/Base.lproj/Main.storyboard index 381493e..5c9dbe1 100644 --- a/Cloudy/Base.lproj/Main.storyboard +++ b/Cloudy/Base.lproj/Main.storyboard @@ -663,7 +663,7 @@ - + @@ -681,7 +681,7 @@