From f129195f7db2a3c14d46f4179afc06d044f01a1b Mon Sep 17 00:00:00 2001 From: Christoffer Winterkvist Date: Sun, 10 Feb 2019 20:17:28 +0100 Subject: [PATCH 1/2] Implement list view mode for applications --- Gray.xcodeproj/project.pbxproj | 28 +++ Podfile | 2 +- Podfile.lock | 11 +- .../Grid.imageset/Contents.json | 17 ++ .../Assets.xcassets/Grid.imageset/Grid.pdf | Bin 0 -> 8624 bytes .../List.imageset/Contents.json | 17 ++ .../Assets.xcassets/List.imageset/List.pdf | Bin 0 -> 7956 bytes Resources/Base.lproj/MainMenu.xib | 14 +- Sources/Application/AppDelegate.swift | 16 +- Sources/Application/LayoutFactory.swift | 11 ++ Sources/Application/Toolbar/Toolbar.swift | 23 ++- .../Application/Toolbar/ViewToolbarItem.swift | 63 +++++++ Sources/Extensions/UserDefaults.swift | 13 ++ .../Applications/AppearanceAware.swift | 69 +++++++ .../Features/Applications/Application.swift | 3 + .../Applications/ApplicationGridView.swift | 61 +------ .../Applications/ApplicationListView.swift | 82 +++++++++ .../ApplicationsFeatureViewController.swift | 169 ++++++++++++++---- .../ApplicationsLogicController.swift | 52 +++--- Sources/Features/Applications/Component.swift | 6 + .../Main/MainContainerViewController.swift | 20 ++- ...ionViewItemComponent-macOS.generated.swift | 140 ++++++++++++++- ...iewControllerFactory-macOS.generated.swift | 4 + .../CollectionViewItemComponent-macOS.stencil | 3 +- 24 files changed, 681 insertions(+), 143 deletions(-) create mode 100644 Resources/Assets.xcassets/Grid.imageset/Contents.json create mode 100644 Resources/Assets.xcassets/Grid.imageset/Grid.pdf create mode 100644 Resources/Assets.xcassets/List.imageset/Contents.json create mode 100644 Resources/Assets.xcassets/List.imageset/List.pdf create mode 100644 Sources/Application/Toolbar/ViewToolbarItem.swift create mode 100644 Sources/Extensions/UserDefaults.swift create mode 100644 Sources/Features/Applications/AppearanceAware.swift create mode 100644 Sources/Features/Applications/ApplicationListView.swift create mode 100644 Sources/Features/Applications/Component.swift diff --git a/Gray.xcodeproj/project.pbxproj b/Gray.xcodeproj/project.pbxproj index cf56964..737cfb6 100644 --- a/Gray.xcodeproj/project.pbxproj +++ b/Gray.xcodeproj/project.pbxproj @@ -41,6 +41,11 @@ BDB20F2B2167A9DB00A72623 /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB20F2A2167A9DB00A72623 /* SearchField.swift */; }; BDBFB09421D0DDB20086F697 /* ApplicationsLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBFB09321D0DDB20086F697 /* ApplicationsLoadingViewController.swift */; }; BDC14C6F21B4502900610983 /* AlertsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC14C6E21B4502900610983 /* AlertsController.swift */; }; + BDE5736022105FDF0065597B /* ApplicationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE5735F22105FDF0065597B /* ApplicationListView.swift */; }; + BDE57364221093440065597B /* Component.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE57363221093440065597B /* Component.swift */; }; + BDE57366221098170065597B /* ViewToolbarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE57365221098170065597B /* ViewToolbarItem.swift */; }; + BDE573682210A2CB0065597B /* AppearanceAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE573672210A2CB0065597B /* AppearanceAware.swift */; }; + BDE5736D2210A9850065597B /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE5736C2210A9850065597B /* UserDefaults.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -86,6 +91,11 @@ BDB20F2A2167A9DB00A72623 /* SearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchField.swift; sourceTree = ""; }; BDBFB09321D0DDB20086F697 /* ApplicationsLoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationsLoadingViewController.swift; sourceTree = ""; }; BDC14C6E21B4502900610983 /* AlertsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertsController.swift; sourceTree = ""; }; + BDE5735F22105FDF0065597B /* ApplicationListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationListView.swift; sourceTree = ""; }; + BDE57363221093440065597B /* Component.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Component.swift; sourceTree = ""; }; + BDE57365221098170065597B /* ViewToolbarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewToolbarItem.swift; sourceTree = ""; }; + BDE573672210A2CB0065597B /* AppearanceAware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceAware.swift; sourceTree = ""; }; + BDE5736C2210A9850065597B /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -122,6 +132,7 @@ children = ( BDC14C6D21B4501B00610983 /* Alerts */, BD30ED4C216C011F00FD2D91 /* Application */, + BDE5736B2210A9780065597B /* Extensions */, BD30ED4B216C010400FD2D91 /* Features */, BDAAC490216A2A810093B27D /* Shared */, BD6F243621620A570032338F /* Utilities */, @@ -246,11 +257,14 @@ BD9D3D79215C2D3F00233333 /* Applications */ = { isa = PBXGroup; children = ( + BDE573672210A2CB0065597B /* AppearanceAware.swift */, BD750FDB215C1AC60024E70A /* Application.swift */, BD1BA9BF215E6DBB0052633B /* ApplicationGridView.swift */, + BDE5735F22105FDF0065597B /* ApplicationListView.swift */, BD9D3D7E215C30B000233333 /* ApplicationsFeatureViewController.swift */, BDBFB09321D0DDB20086F697 /* ApplicationsLoadingViewController.swift */, BD308449215C198D002A9349 /* ApplicationsLogicController.swift */, + BDE57363221093440065597B /* Component.swift */, ); path = Applications; sourceTree = ""; @@ -288,6 +302,7 @@ BDB20F252167A64100A72623 /* Toolbar.swift */, BDB20F282167A73C00A72623 /* SearchToolbarItem.swift */, BDB20F2A2167A9DB00A72623 /* SearchField.swift */, + BDE57365221098170065597B /* ViewToolbarItem.swift */, ); path = Toolbar; sourceTree = ""; @@ -300,6 +315,14 @@ path = Alerts; sourceTree = ""; }; + BDE5736B2210A9780065597B /* Extensions */ = { + isa = PBXGroup; + children = ( + BDE5736C2210A9850065597B /* UserDefaults.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -450,6 +473,7 @@ buildActionMask = 2147483647; files = ( BD2349312207446D0047F910 /* StatefulItem.swift in Sources */, + BDE57364221093440065597B /* Component.swift in Sources */, BDB20F2B2167A9DB00A72623 /* SearchField.swift in Sources */, BDB20F292167A73C00A72623 /* SearchToolbarItem.swift in Sources */, BD23492D220744330047F910 /* ViewControllerFactory-macOS.generated.swift in Sources */, @@ -461,13 +485,17 @@ BD67CE1C215BF4FE00216FDB /* MainContainerViewController.swift in Sources */, BD23492E220744330047F910 /* CollectionViewItemComponent-macOS.generated.swift in Sources */, BD67CE0E215BF15F00216FDB /* AppDelegate.swift in Sources */, + BDE57366221098170065597B /* ViewToolbarItem.swift in Sources */, BD30844A215C198D002A9349 /* ApplicationsLogicController.swift in Sources */, BDAAC492216A2A920093B27D /* CollectionViewHeader.swift in Sources */, BD1D9545215E435F003ABBCF /* VersionController.swift in Sources */, + BDE5736022105FDF0065597B /* ApplicationListView.swift in Sources */, BD9F0C462166764E00608FD9 /* SystemPreference.swift in Sources */, BDC14C6F21B4502900610983 /* AlertsController.swift in Sources */, BDB20F262167A64100A72623 /* Toolbar.swift in Sources */, + BDE5736D2210A9850065597B /* UserDefaults.swift in Sources */, BD9F0C4C2166773300608FD9 /* SystemPreferenceView.swift in Sources */, + BDE573682210A2CB0065597B /* AppearanceAware.swift in Sources */, BD9F0C43216675F300608FD9 /* SystemPreferenceFeatureViewController.swift in Sources */, BD23492F220744330047F910 /* StatefulItem-macOS.generated.swift in Sources */, BD2349302207446D0047F910 /* CollectionViewItemComponent.swift in Sources */, diff --git a/Podfile b/Podfile index f9dc876..7cda3dc 100644 --- a/Podfile +++ b/Podfile @@ -4,7 +4,7 @@ platform :macos, '10.14' # Frameworks pod 'Blueprints' pod 'Differific' -pod 'Family' +pod 'Family', path: '_pods/Family' pod 'UserInterface' pod 'Sourcery' diff --git a/Podfile.lock b/Podfile.lock index fa5dae9..e919873 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -8,7 +8,7 @@ PODS: DEPENDENCIES: - Blueprints - Differific - - Family + - Family (from `_pods/Family`) - Sourcery - UserInterface @@ -16,10 +16,13 @@ SPEC REPOS: https://github.com/cocoapods/specs.git: - Blueprints - Differific - - Family - Sourcery - UserInterface +EXTERNAL SOURCES: + Family: + :path: _pods/Family + SPEC CHECKSUMS: Blueprints: 6dea8aa1e682d4ee405b83502569011e8640f394 Differific: b26ecab6daa1a2a93efeee41cec5e9593ced09bc @@ -27,6 +30,6 @@ SPEC CHECKSUMS: Sourcery: 5895672cae353cdbfa95f3f4aaeb75a664d76f6a UserInterface: 54e15db9aceaec2b9686d00f471adb15d2598ea3 -PODFILE CHECKSUM: 9a4a4208e8595d7fa51cbc758521284dec9359cc +PODFILE CHECKSUM: 5860d322941de887dd5e7287667d76345e73097d -COCOAPODS: 1.6.0.rc.2 +COCOAPODS: 1.6.0 diff --git a/Resources/Assets.xcassets/Grid.imageset/Contents.json b/Resources/Assets.xcassets/Grid.imageset/Contents.json new file mode 100644 index 0000000..f654343 --- /dev/null +++ b/Resources/Assets.xcassets/Grid.imageset/Contents.json @@ -0,0 +1,17 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Grid.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template", + "preserves-vector-representation" : true, + "auto-scaling" : "auto" + } +} \ No newline at end of file diff --git a/Resources/Assets.xcassets/Grid.imageset/Grid.pdf b/Resources/Assets.xcassets/Grid.imageset/Grid.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d098ddde253d79c481cbebb12be521ecdce21416 GIT binary patch literal 8624 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 nP(C<>9|VF5i4y;J5e&8RGu~0`v4` literal 0 HcmV?d00001 diff --git a/Resources/Base.lproj/MainMenu.xib b/Resources/Base.lproj/MainMenu.xib index b3dd971..f5bd2d7 100644 --- a/Resources/Base.lproj/MainMenu.xib +++ b/Resources/Base.lproj/MainMenu.xib @@ -1,7 +1,7 @@ - + - + @@ -470,6 +470,16 @@ + + + + + + + + + + diff --git a/Sources/Application/AppDelegate.swift b/Sources/Application/AppDelegate.swift index e2e1022..ad5c5f0 100644 --- a/Sources/Application/AppDelegate.swift +++ b/Sources/Application/AppDelegate.swift @@ -29,7 +29,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { let dependencyContainer = DependencyContainer() let contentViewController = MainContainerViewController(iconStore: dependencyContainer) let toolbar = Toolbar(identifier: .init("MainApplicationWindowToolbar")) - toolbar.searchDelegate = contentViewController + toolbar.toolbarDelegate = contentViewController let windowSize = CGSize(width: 400, height: 640) let window = NSWindow(contentViewController: contentViewController) window.setFrameAutosaveName(NSWindow.FrameAutosaveName.init("MainApplicationWindow")) @@ -64,6 +64,20 @@ class AppDelegate: NSObject, NSApplicationDelegate { // MARK: - Actions + @IBAction func switchToGrid(_ sender: Any?) { + guard let toolbar = window?.toolbar as? Toolbar else { return } + (window?.contentViewController as? MainContainerViewController)?.toolbar(toolbar, + didChangeMode: ApplicationsFeatureViewController.Mode.grid.rawValue) + NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "featureViewControllerMode"), object: nil) + } + + @IBAction func switchToList(_ sender: Any?) { + guard let toolbar = window?.toolbar as? Toolbar else { return } + (window?.contentViewController as? MainContainerViewController)?.toolbar(toolbar, + didChangeMode: ApplicationsFeatureViewController.Mode.list.rawValue) + NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "featureViewControllerMode"), object: nil) + } + @IBAction func search(_ sender: Any?) { toolbar?.searchField?.becomeFirstResponder() } diff --git a/Sources/Application/LayoutFactory.swift b/Sources/Application/LayoutFactory.swift index eeac3f0..dacd075 100644 --- a/Sources/Application/LayoutFactory.swift +++ b/Sources/Application/LayoutFactory.swift @@ -11,4 +11,15 @@ class LayoutFactory { animator: DefaultLayoutAnimator(animation: .fade)) return layout } + + func createListLayout() -> VerticalBlueprintLayout { + let layout = VerticalBlueprintLayout( + itemsPerRow: 1.0, + height: 50, + minimumInteritemSpacing: 10, + minimumLineSpacing: 10, + sectionInset: .init(top: 0, left: 10, bottom: 20, right: 10), + animator: DefaultLayoutAnimator(animation: .fade)) + return layout + } } diff --git a/Sources/Application/Toolbar/Toolbar.swift b/Sources/Application/Toolbar/Toolbar.swift index 4f94ef1..38945c8 100644 --- a/Sources/Application/Toolbar/Toolbar.swift +++ b/Sources/Application/Toolbar/Toolbar.swift @@ -1,11 +1,12 @@ import Cocoa -protocol ToolbarSearchDelegate: class { +protocol ToolbarDelegate: class { func toolbar(_ toolbar: Toolbar, didSearchFor string: String) + func toolbar(_ toolbar: Toolbar, didChangeMode mode: String) } -class Toolbar: NSToolbar, NSToolbarDelegate { - weak var searchDelegate: ToolbarSearchDelegate? +class Toolbar: NSToolbar, NSToolbarDelegate, ViewToolbarItemDelegate { + weak var toolbarDelegate: ToolbarDelegate? weak var searchField: SearchField? override init(identifier: NSToolbar.Identifier) { @@ -19,19 +20,25 @@ class Toolbar: NSToolbar, NSToolbarDelegate { return [ NSToolbarItem.Identifier.space, NSToolbarItem.Identifier.flexibleSpace, + ViewToolbarItem.itemIdentifier, SearchToolbarItem.itemIdentifier ] } func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { return [ + ViewToolbarItem.itemIdentifier, NSToolbarItem.Identifier.flexibleSpace, - SearchToolbarItem.itemIdentifier + SearchToolbarItem.itemIdentifier, ] } func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { switch itemIdentifier { + case ViewToolbarItem.itemIdentifier: + let viewToolbarItem = ViewToolbarItem() + viewToolbarItem.delegate = self + return viewToolbarItem case SearchToolbarItem.itemIdentifier: let searchToolbarItem = SearchToolbarItem(text: "") searchToolbarItem.titleLabel.target = self @@ -46,6 +53,12 @@ class Toolbar: NSToolbar, NSToolbarDelegate { } @objc func search(_ label: SearchField) { - searchDelegate?.toolbar(self, didSearchFor: label.stringValue) + toolbarDelegate?.toolbar(self, didSearchFor: label.stringValue) + } + + // MARK: - ViewToolbarItemDelegate + + func viewToolbarItem(_ toolbarItem: ViewToolbarItem, didChange mode: String) { + toolbarDelegate?.toolbar(self, didChangeMode: mode) } } diff --git a/Sources/Application/Toolbar/ViewToolbarItem.swift b/Sources/Application/Toolbar/ViewToolbarItem.swift new file mode 100644 index 0000000..35e58d9 --- /dev/null +++ b/Sources/Application/Toolbar/ViewToolbarItem.swift @@ -0,0 +1,63 @@ +import Cocoa + +protocol ViewToolbarItemDelegate: class { + func viewToolbarItem(_ toolbarItem: ViewToolbarItem, didChange mode: String) +} + +class ViewToolbarItem: NSToolbarItem { + static var itemIdentifier: NSToolbarItem.Identifier = .init("View") + + weak var delegate: ViewToolbarItemDelegate? + + lazy var segmentedControl = NSSegmentedControl.init(images: ApplicationsFeatureViewController.Mode.allCases.compactMap({ $0.image }), + trackingMode: .selectOne, + target: self, + action: #selector(didChangeView(_:))) + lazy var customView = NSView() + + init() { + super.init(itemIdentifier: ViewToolbarItem.itemIdentifier) + view = customView + view?.addSubview(segmentedControl) + minSize = .init(width: 80, height: 25) + maxSize = .init(width: 80, height: 25) + segmentedControl.setToolTip("Grid", forSegment: 0) + segmentedControl.setTag(0, forSegment: 0) + segmentedControl.setToolTip("List", forSegment: 1) + segmentedControl.setTag(1, forSegment: 1) + configureSegmentControl() + setupConstraints() + + NotificationCenter.default.addObserver(self, selector: #selector(configureSegmentControl), + name: NSNotification.Name.init(rawValue: "featureViewControllerMode"), object: nil) + } + + @objc func configureSegmentControl() { + if let mode = UserDefaults.standard.featureViewControllerMode { + switch mode { + case .grid: + segmentedControl.selectSegment(withTag: 0) + case .list: + segmentedControl.selectSegment(withTag: 1) + } + } else { + segmentedControl.selectSegment(withTag: 0) + } + } + + func setupConstraints() { + segmentedControl.translatesAutoresizingMaskIntoConstraints = false + segmentedControl.centerXAnchor.constraint(equalTo: customView.centerXAnchor).isActive = true + segmentedControl.centerYAnchor.constraint(equalTo: customView.centerYAnchor).isActive = true + segmentedControl.widthAnchor.constraint(equalTo: customView.widthAnchor).isActive = true + segmentedControl.heightAnchor.constraint(equalToConstant: 25).isActive = true + } + + @objc func didChangeView(_ segmentControl: NSSegmentedControl) { + guard let label = segmentedControl.toolTip(forSegment: segmentedControl.indexOfSelectedItem) else { + return + } + + delegate?.viewToolbarItem(self, didChange: label) + } +} diff --git a/Sources/Extensions/UserDefaults.swift b/Sources/Extensions/UserDefaults.swift new file mode 100644 index 0000000..000be69 --- /dev/null +++ b/Sources/Extensions/UserDefaults.swift @@ -0,0 +1,13 @@ +import Foundation + +extension UserDefaults { + var featureViewControllerMode: ApplicationsFeatureViewController.Mode? { + get { + let rawValue = UserDefaults.standard.string(forKey: #function) ?? "" + return ApplicationsFeatureViewController.Mode.init(rawValue: rawValue) + } + set { + UserDefaults.standard.set(newValue?.rawValue, forKey: #function) + } + } +} diff --git a/Sources/Features/Applications/AppearanceAware.swift b/Sources/Features/Applications/AppearanceAware.swift new file mode 100644 index 0000000..b7de085 --- /dev/null +++ b/Sources/Features/Applications/AppearanceAware.swift @@ -0,0 +1,69 @@ +import Cocoa + +protocol AppearanceAware { + var titleLabel: NSTextField { get } + var subtitleLabel: NSTextField { get } + var view: NSView { get } + func update(with appearance: Application.Appearance, duration: TimeInterval, then handler: (() -> Void)?) +} + +extension AppearanceAware { + func update(with appearance: Application.Appearance, duration: TimeInterval = 0, then handler: (() -> Void)? = nil) { + if duration > 0 { + NSAnimationContext.current.allowsImplicitAnimation = true + NSAnimationContext.runAnimationGroup({ (context) in + context.duration = duration + switch appearance { + case .dark: + view.animator().layer?.backgroundColor = NSColor(named: "Dark")?.cgColor + titleLabel.animator().textColor = .white + subtitleLabel.animator().textColor = .controlAccentColor + view.layer?.borderWidth = 0.0 + case .system: + view.animator().layer?.backgroundColor = NSColor.gray.cgColor + titleLabel.animator().textColor = .white + subtitleLabel.animator().textColor = .lightGray + view.layer?.borderWidth = 0.0 + case .light: + view.animator().layer?.backgroundColor = .white + titleLabel.animator().textColor = .black + subtitleLabel.animator().textColor = .controlAccentColor + view.layer?.borderColor = NSColor.gray.withAlphaComponent(0.25).cgColor + view.layer?.borderWidth = 0 + } + }, completionHandler:{ + handler?() + }) + } else { + switch appearance { + case .dark: + view.layer?.backgroundColor = NSColor(named: "Dark")?.cgColor + titleLabel.textColor = .white + subtitleLabel.textColor = .controlAccentColor + view.layer?.borderWidth = 0.0 + case .light: + view.layer?.backgroundColor = NSColor(named: "Light")?.cgColor + titleLabel.textColor = .black + subtitleLabel.textColor = .controlAccentColor + view.layer?.borderColor = NSColor.gray.withAlphaComponent(0.25).cgColor + view.layer?.borderWidth = 1.0 + case .system: + switch view.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) { + case .darkAqua?: + view.layer?.backgroundColor = NSColor(named: "Dark")?.cgColor + titleLabel.textColor = .white + subtitleLabel.textColor = .lightGray + view.layer?.borderWidth = 0.0 + case .aqua?: + view.layer?.backgroundColor = NSColor(named: "Light")?.cgColor + titleLabel.textColor = .black + subtitleLabel.textColor = .controlAccentColor + view.layer?.borderColor = NSColor.gray.withAlphaComponent(0.25).cgColor + view.layer?.borderWidth = 1.0 + default: + break + } + } + } + } +} diff --git a/Sources/Features/Applications/Application.swift b/Sources/Features/Applications/Application.swift index 0a369bf..675947a 100644 --- a/Sources/Features/Applications/Application.swift +++ b/Sources/Features/Applications/Application.swift @@ -9,16 +9,19 @@ struct Application: Hashable { let bundleIdentifier: String let name: String + let metadata: String let url: URL let preferencesUrl: URL let appearance: Appearance let restricted: Bool init(bundleIdentifier: String, name: String, + metadata: String, url: URL, preferencesUrl: URL, appearance: Appearance, restricted: Bool) { self.bundleIdentifier = bundleIdentifier self.name = name + self.metadata = metadata self.url = url self.preferencesUrl = preferencesUrl self.appearance = appearance diff --git a/Sources/Features/Applications/ApplicationGridView.swift b/Sources/Features/Applications/ApplicationGridView.swift index d587dd5..d2eed48 100644 --- a/Sources/Features/Applications/ApplicationGridView.swift +++ b/Sources/Features/Applications/ApplicationGridView.swift @@ -6,7 +6,7 @@ protocol ApplicationGridViewDelegate: class { } // sourcery: let application = Application -class ApplicationGridView: NSCollectionViewItem, CollectionViewItemComponent { +class ApplicationGridView: NSCollectionViewItem, CollectionViewItemComponent, AppearanceAware { lazy var baseView = NSView() weak var delegate: ApplicationGridViewDelegate? @@ -85,64 +85,5 @@ class ApplicationGridView: NSCollectionViewItem, CollectionViewItemComponent { @objc func resetApplication() { delegate?.applicationView(self, didResetApplication: currentAppearance) } - - func update(with appearance: Application.Appearance, duration: TimeInterval = 0, then handler: (() -> Void)? = nil) { - if duration > 0 { - NSAnimationContext.current.allowsImplicitAnimation = true - NSAnimationContext.runAnimationGroup({ (context) in - context.duration = duration - switch appearance { - case .dark: - view.animator().layer?.backgroundColor = NSColor(named: "Dark")?.cgColor - titleLabel.animator().textColor = .white - subtitleLabel.animator().textColor = .controlAccentColor - view.layer?.borderWidth = 0.0 - case .system: - view.animator().layer?.backgroundColor = NSColor.gray.cgColor - titleLabel.animator().textColor = .white - subtitleLabel.animator().textColor = .lightGray - view.layer?.borderWidth = 0.0 - case .light: - view.animator().layer?.backgroundColor = .white - titleLabel.animator().textColor = .black - subtitleLabel.animator().textColor = .controlAccentColor - view.layer?.borderColor = NSColor.gray.withAlphaComponent(0.25).cgColor - view.layer?.borderWidth = 0 - } - }, completionHandler:{ - handler?() - }) - } else { - switch appearance { - case .dark: - view.layer?.backgroundColor = NSColor(named: "Dark")?.cgColor - titleLabel.textColor = .white - subtitleLabel.textColor = .controlAccentColor - view.layer?.borderWidth = 0.0 - case .light: - view.layer?.backgroundColor = NSColor(named: "Light")?.cgColor - titleLabel.textColor = .black - subtitleLabel.textColor = .controlAccentColor - view.layer?.borderColor = NSColor.gray.withAlphaComponent(0.25).cgColor - view.layer?.borderWidth = 1.0 - case .system: - switch view.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) { - case .darkAqua?: - view.layer?.backgroundColor = NSColor(named: "Dark")?.cgColor - titleLabel.textColor = .white - subtitleLabel.textColor = .lightGray - view.layer?.borderWidth = 0.0 - case .aqua?: - view.layer?.backgroundColor = NSColor(named: "Light")?.cgColor - titleLabel.textColor = .black - subtitleLabel.textColor = .controlAccentColor - view.layer?.borderColor = NSColor.gray.withAlphaComponent(0.25).cgColor - view.layer?.borderWidth = 1.0 - default: - break - } - } - } - } } diff --git a/Sources/Features/Applications/ApplicationListView.swift b/Sources/Features/Applications/ApplicationListView.swift new file mode 100644 index 0000000..136e6dc --- /dev/null +++ b/Sources/Features/Applications/ApplicationListView.swift @@ -0,0 +1,82 @@ +import Cocoa + +protocol ApplicationListViewDelegate: class { + func applicationView(_ view: ApplicationListView, didResetApplication currentAppearance: Application.Appearance?) +} + +// sourcery: let application = Application +class ApplicationListView: NSCollectionViewItem, CollectionViewItemComponent, AppearanceAware { + let baseView = NSView() + weak var delegate: ApplicationListViewDelegate? + + // sourcery: currentAppearance = model.application.appearance + var currentAppearance: Application.Appearance? { + didSet { + if let currentAppearance = self.currentAppearance { + update(with: currentAppearance) + } + } + } + + // sourcery: $RawBinding = "iconStore.loadIcon(for: model.application) { image in view.iconView.image = image }" + lazy var iconView: NSImageView = .init() + // sourcery: let title: String = "titleLabel.stringValue = model.title" + lazy var titleLabel: NSTextField = .init() + // sourcery: let subtitle: String = "subtitleLabel.stringValue = model.subtitle" + lazy var subtitleLabel: NSTextField = .init() + + private var layoutConstraints = [NSLayoutConstraint]() + + override func loadView() { + self.view = baseView + self.view.wantsLayer = true + } + + override func viewDidLoad() { + super.viewDidLoad() + + let menu = NSMenu() + menu.addItem(NSMenuItem(title: "Reset", action: #selector(resetApplication), keyEquivalent: "")) + view.menu = menu + + let verticalStackView = NSStackView(views: [titleLabel, subtitleLabel]) + verticalStackView.alignment = .leading + verticalStackView.orientation = .vertical + verticalStackView.spacing = 0 + let stackView = NSStackView(views: [iconView, verticalStackView]) + stackView.orientation = .horizontal + + titleLabel.isEditable = false + titleLabel.drawsBackground = false + titleLabel.isBezeled = false + titleLabel.font = NSFont.boldSystemFont(ofSize: 13) + + subtitleLabel.isEditable = false + subtitleLabel.drawsBackground = false + subtitleLabel.isBezeled = false + + stackView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(stackView) + view.layer?.cornerRadius = 4 + + NSLayoutConstraint.deactivate(layoutConstraints) + layoutConstraints = [ + stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8), + stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8), + stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8), + stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8) + ] + NSLayoutConstraint.activate(layoutConstraints) + } + + override func viewDidLayout() { + super.viewDidLayout() + + guard let currentAppearance = currentAppearance else { return } + update(with: currentAppearance) + } + + @objc func resetApplication() { + delegate?.applicationView(self, didResetApplication: currentAppearance) + } +} diff --git a/Sources/Features/Applications/ApplicationsFeatureViewController.swift b/Sources/Features/Applications/ApplicationsFeatureViewController.swift index 28ae765..29526a4 100644 --- a/Sources/Features/Applications/ApplicationsFeatureViewController.swift +++ b/Sources/Features/Applications/ApplicationsFeatureViewController.swift @@ -6,34 +6,74 @@ protocol ApplicationsFeatureViewControllerDelegate: class { func applicationViewController(_ controller: ApplicationsFeatureViewController, finishedLoading: Bool) func applicationViewController(_ controller: ApplicationsFeatureViewController, - didLoad application: ApplicationGridViewModel, + didLoad application: Application, offset: Int, total: Int) func applicationViewController(_ controller: ApplicationsFeatureViewController, toggleAppearance newAppearance: Application.Appearance, - application: ApplicationGridViewModel) + application: Application) } class ApplicationsFeatureViewController: NSViewController, NSCollectionViewDelegate, - ApplicationGridViewDelegate, ApplicationsLogicControllerDelegate { +ApplicationGridViewDelegate, ApplicationsLogicControllerDelegate, ApplicationListViewDelegate { + enum Mode: String, CaseIterable { + case grid = "Grid" + case list = "List" + + var image: NSImage { + switch self { + case .grid: + return NSImage.init(named: "Grid")! + case .list: + return NSImage.init(named: "List")! + } + } + } + enum State { - case loading(application: ApplicationGridViewModel, offset: Int, total: Int) - case view([ApplicationGridViewModel]) + case loading(application: Application, offset: Int, total: Int) + case view([Application]) } weak var delegate: ApplicationsFeatureViewControllerDelegate? - let component: ApplicationGridViewController + let listComponent: ApplicationListViewController + let gridComponent: ApplicationGridViewController let logicController = ApplicationsLogicController() let iconStore: IconStore - var applicationCache = [ApplicationGridViewModel]() + var mode: Mode { + didSet { + switch mode { + case .grid: + self.component = gridComponent + case .list: + self.component = listComponent + } + self.view = component.view + configureComponent() + } + } + var component: Component + var applicationCache = [Application]() var query: String = "" - init(iconStore: IconStore, models: [Application] = []) { + init(iconStore: IconStore, mode: Mode?, models: [Application] = []) { let layoutFactory = LayoutFactory() self.iconStore = iconStore - self.component = ApplicationGridViewController(title: "Applications", - layout: layoutFactory.createGridLayout(), - iconStore: iconStore) + self.mode = mode ?? .grid + self.gridComponent = ApplicationGridViewController(title: "Applications", + layout: layoutFactory.createGridLayout(), + iconStore: iconStore) + self.listComponent = ApplicationListViewController(title: "Applications", + layout: layoutFactory.createListLayout(), + iconStore: iconStore) + + switch self.mode { + case .grid: + self.component = gridComponent + case .list: + self.component = listComponent + } + super.init(nibName: nil, bundle: nil) } @@ -48,9 +88,7 @@ class ApplicationsFeatureViewController: NSViewController, NSCollectionViewDeleg override func viewDidLoad() { super.viewDidLoad() logicController.delegate = self - component.collectionView.delegate = self - component.collectionView.isSelectable = true - component.collectionView.allowsMultipleSelection = false + configureComponent() } override func viewDidAppear() { @@ -58,20 +96,44 @@ class ApplicationsFeatureViewController: NSViewController, NSCollectionViewDeleg logicController.load() } - func toggle(_ newAppearance: Application.Appearance, for model: ApplicationGridViewModel) { + func configureComponent() { + component.collectionView.delegate = self + component.collectionView.isSelectable = true + component.collectionView.allowsMultipleSelection = false + } + + func toggle(_ newAppearance: Application.Appearance, for model: Application) { logicController.toggleAppearance(newAppearance, for: model) } func performSearch(with string: String) { query = string.lowercased() + let filtered: [Application] switch string.count { case 0: - component.reload(with: applicationCache) + filtered = applicationCache default: - // This can be improved! - let results = applicationCache.filter({ $0.application.name.lowercased().contains(query) }) - component.reload(with: results) + filtered = applicationCache.filter({ $0.name.lowercased().contains(query) }) } + + switch mode { + case .grid: + gridComponent.reload(with: gridModels(from: filtered)) + case .list: + listComponent.reload(with: listModels(from: filtered)) + } + } + + private func listModels(from applications: [Application]) -> [ApplicationListViewModel] { + return applications.compactMap({ + ApplicationListViewModel(title: $0.name, subtitle: $0.metadata, application: $0) + }) + } + + private func gridModels(from applications: [Application]) -> [ApplicationGridViewModel] { + return applications.compactMap({ + ApplicationGridViewModel(title: $0.name, subtitle: $0.metadata, application: $0) + }) } private func render(_ newState: State) { @@ -81,10 +143,18 @@ class ApplicationsFeatureViewController: NSViewController, NSCollectionViewDeleg case .view(let applications): delegate?.applicationViewController(self, finishedLoading: true) applicationCache = applications - component.reload(with: applications) { [weak self] in + + let completion = { [weak self] in guard let strongSelf = self else { return } strongSelf.performSearch(with: strongSelf.query) } + + switch mode { + case .grid: + gridComponent.reload(with: gridModels(from: applications), completion: completion) + case .list: + listComponent.reload(with: listModels(from: applications), completion: completion) + } } } @@ -103,21 +173,28 @@ class ApplicationsFeatureViewController: NSViewController, NSCollectionViewDeleg // MARK: - ApplicationsLogicControllerDelegate - func applicationsLogicController(_ controller: ApplicationsLogicController, didLoadApplication application: ApplicationGridViewModel, offset: Int, total: Int) { + func applicationsLogicController(_ controller: ApplicationsLogicController, didLoadApplication application: Application, offset: Int, total: Int) { render(.loading(application: application, offset: offset, total: total)) } - func applicationsLogicController(_ controller: ApplicationsLogicController, didLoadApplications applications: [ApplicationGridViewModel]) { + func applicationsLogicController(_ controller: ApplicationsLogicController, didLoadApplications applications: [Application]) { render(.view(applications)) } // MARK: - ApplicationGridViewDelegate func applicationView(_ view: ApplicationGridView, didResetApplication currentAppearance: Application.Appearance?) { - guard let indexPath = component.indexPath(for: view) else { return } + guard let indexPath = component.collectionView.indexPath(for: view) else { return } + let model = gridComponent.model(at: indexPath) + toggle(.system, for: model.application) + } + + // MARK: - ApplicationListViewDelegate - let model = component.model(at: indexPath) - toggle(.system, for: model) + func applicationView(_ view: ApplicationListView, didResetApplication currentAppearance: Application.Appearance?) { + guard let indexPath = component.collectionView.indexPath(for: view) else { return } + let model = gridComponent.model(at: indexPath) + toggle(.system, for: model.application) } // MARK: - NSCollectionViewDelegate @@ -126,20 +203,38 @@ class ApplicationsFeatureViewController: NSViewController, NSCollectionViewDeleg if let view = item as? ApplicationGridView { view.delegate = self } + + if let view = item as? ApplicationListView { + view.delegate = self + } } func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) { - guard let indexPath = indexPaths.first, - let item = collectionView.item(at: indexPath) as? ApplicationGridView else { - return - } + guard let indexPath = indexPaths.first else { return } + guard let item: NSCollectionViewItem = collectionView.item(at: indexPath) else { return } collectionView.deselectAll(nil) - let model = component.model(at: indexPath) - let newAppearance: Application.Appearance = model.application.appearance == .light - ? .dark - : .light + let restricted: Bool + let application: Application + let newAppearance: Application.Appearance + + if collectionView.item(at: indexPath) is ApplicationGridView { + let model = gridComponent.model(at: indexPath) + restricted = model.application.restricted + application = model.application + newAppearance = model.application.appearance == .light + ? .dark + : .light + } else if collectionView.item(at: indexPath) is ApplicationListView { + let model = listComponent.model(at: indexPath) + restricted = model.application.restricted + application = model.application + newAppearance = model.application.appearance == .light + ? .dark + : .light + } else { return } + let duration: TimeInterval = 0.15 NSAnimationContext.runAnimationGroup({ (context) in @@ -158,18 +253,18 @@ class ApplicationsFeatureViewController: NSViewController, NSCollectionViewDeleg context.allowsImplicitAnimation = true item.view.animator().layer?.setAffineTransform(.identity) }, completionHandler: { - if model.application.restricted { - self.showPermissionsDialog(for: model.application) { result in + if restricted { + self.showPermissionsDialog(for: application) { result in guard result else { return } let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")! NSWorkspace.shared.open(url) } } else { - item.update(with: newAppearance, duration: 0.5) { [weak self] in + (item as? AppearanceAware)?.update(with: newAppearance, duration: 0.5) { [weak self] in guard let strongSelf = self else { return } strongSelf.delegate?.applicationViewController(strongSelf, toggleAppearance: newAppearance, - application: model) + application: application) } } }) diff --git a/Sources/Features/Applications/ApplicationsLogicController.swift b/Sources/Features/Applications/ApplicationsLogicController.swift index aeb2fcf..7fd1594 100644 --- a/Sources/Features/Applications/ApplicationsLogicController.swift +++ b/Sources/Features/Applications/ApplicationsLogicController.swift @@ -3,9 +3,9 @@ import Cocoa protocol ApplicationsLogicControllerDelegate: class { func applicationsLogicController(_ controller: ApplicationsLogicController, - didLoadApplication application: ApplicationGridViewModel, + didLoadApplication application: Application, offset: Int, total: Int) - func applicationsLogicController(_ controller: ApplicationsLogicController, didLoadApplications applications: [ApplicationGridViewModel]) + func applicationsLogicController(_ controller: ApplicationsLogicController, didLoadApplications applications: [Application]) } class ApplicationsLogicController { @@ -40,10 +40,9 @@ class ApplicationsLogicController { } func toggleAppearance(_ newAppearance: Application.Appearance, - for model: ApplicationGridViewModel) { + for application: Application) { queue.async { [weak self] in let shell = Shell() - let application = model.application // The cfprefsd is killed for the current user to avoid plist caching. // PlistBuddy is used to set new values. @@ -151,8 +150,8 @@ class ApplicationsLogicController { } private func parseApplicationUrls(_ appUrls: [URL], - excludedBundles: [String] = []) throws -> [ApplicationGridViewModel] { - var applications = [ApplicationGridViewModel]() + excludedBundles: [String] = []) throws -> [Application] { + var applications = [Application]() let shell = Shell() let sip = shell.execute(command: "csrutil status").contains("enabled") let libraryDirectory = try FileManager.default.url(for: .libraryDirectory, @@ -194,38 +193,41 @@ class ApplicationsLogicController { FileManager.default.fileExists(atPath: appContainerPreferenceUrl.path) && NSDictionary.init(contentsOfFile: appContainerPreferenceUrl.path) == nil - let application = Application(bundleIdentifier: bundleIdentifier, - name: bundleName, url: url, - preferencesUrl: resolvedAppPreferenceUrl, - appearance: applicationPlist?.appearance() ?? .system, - restricted: restricted) - var subtitle: String - switch application.appearance { + let appearance = applicationPlist?.appearance() ?? .system + var metadata: String + switch appearance { case .dark: - subtitle = "Dark appearance" + metadata = "Dark appearance" case .light: - subtitle = "Light appearance" + metadata = "Light appearance" case .system: - subtitle = "System appearance" + metadata = "System appearance" } - if application.restricted { - subtitle = "🔐 Locked" + if restricted { + metadata = "🔐 Locked" } - let app = ApplicationGridViewModel( - title: bundleName, - subtitle: subtitle, - application: application) + let application = Application(bundleIdentifier: bundleIdentifier, + name: bundleName, metadata: metadata, + url: url, + preferencesUrl: resolvedAppPreferenceUrl, + appearance: appearance, + restricted: restricted) + +// let app = ApplicationGridViewModel( +// title: bundleName, +// subtitle: subtitle, +// application: application) DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { return } - strongSelf.delegate?.applicationsLogicController(strongSelf, didLoadApplication: app, offset: offset, total: total) + strongSelf.delegate?.applicationsLogicController(strongSelf, didLoadApplication: application, offset: offset, total: total) } - applications.append(app) + applications.append(application) addedApplicationNames.append(bundleName) } - return applications.sorted(by: { $0.application.name.lowercased() < $1.application.name.lowercased() }) + return applications.sorted(by: { $0.name.lowercased() < $1.name.lowercased() }) } private func shouldExcludeApplication(with plist: NSDictionary, applicationUrl url: URL) -> Bool { diff --git a/Sources/Features/Applications/Component.swift b/Sources/Features/Applications/Component.swift new file mode 100644 index 0000000..25bd1d0 --- /dev/null +++ b/Sources/Features/Applications/Component.swift @@ -0,0 +1,6 @@ +import Cocoa + +protocol Component { + var collectionView: NSCollectionView { get } + var view: NSView { get } +} diff --git a/Sources/Features/Main/MainContainerViewController.swift b/Sources/Features/Main/MainContainerViewController.swift index a2c961a..dcff47d 100644 --- a/Sources/Features/Main/MainContainerViewController.swift +++ b/Sources/Features/Main/MainContainerViewController.swift @@ -4,7 +4,7 @@ import Family class MainContainerViewController: FamilyViewController, ApplicationsFeatureViewControllerDelegate, SystemPreferenceFeatureViewControllerDelegate, -ToolbarSearchDelegate { +ToolbarDelegate { lazy var loadingLabelController = ApplicationsLoadingViewController(text: "Loading...") let preferencesViewController: SystemPreferenceFeatureViewController let applicationsViewController: ApplicationsFeatureViewController @@ -12,7 +12,8 @@ ToolbarSearchDelegate { init(iconStore: IconStore) { self.preferencesViewController = SystemPreferenceFeatureViewController(iconStore: iconStore) - self.applicationsViewController = ApplicationsFeatureViewController(iconStore: iconStore) + self.applicationsViewController = ApplicationsFeatureViewController(iconStore: iconStore, + mode: UserDefaults.standard.featureViewControllerMode) super.init(nibName: nil, bundle: nil) } @@ -59,6 +60,16 @@ ToolbarSearchDelegate { performSearch(with: string) } + func toolbar(_ toolbar: Toolbar, didChangeMode mode: String) { + guard let mode = ApplicationsFeatureViewController.Mode.init(rawValue: mode) else { + return + } + UserDefaults.standard.featureViewControllerMode = mode + applicationsViewController.mode = mode + applicationsViewController.removeFromParent() + addChild(applicationsViewController) + } + // MARK: - ApplicationCollectionViewControllerDelegate func applicationViewController(_ controller: ApplicationsFeatureViewController, finishedLoading: Bool) { @@ -66,8 +77,7 @@ ToolbarSearchDelegate { } func applicationViewController(_ controller: ApplicationsFeatureViewController, - didLoad application: ApplicationGridViewModel, offset: Int, total: Int) { - let application = application.application + didLoad application: Application, offset: Int, total: Int) { let progress = Double(offset + 1) / Double(total) * Double(100) loadingLabelController.progress.doubleValue = floor(progress) loadingLabelController.textField.stringValue = "Loading (\(offset)/\(total)): \(application.name)" @@ -75,7 +85,7 @@ ToolbarSearchDelegate { func applicationViewController(_ controller: ApplicationsFeatureViewController, toggleAppearance newAppearance: Application.Appearance, - application: ApplicationGridViewModel) { + application: Application) { applicationsViewController.toggle(newAppearance, for: application) } diff --git a/Voodoo/Output/CollectionViewItemComponent-macOS.generated.swift b/Voodoo/Output/CollectionViewItemComponent-macOS.generated.swift index 4a572dc..b83bd42 100644 --- a/Voodoo/Output/CollectionViewItemComponent-macOS.generated.swift +++ b/Voodoo/Output/CollectionViewItemComponent-macOS.generated.swift @@ -4,7 +4,7 @@ import Cocoa import Differific -class ApplicationGridViewController: NSViewController { +class ApplicationGridViewController: NSViewController, Component { private let layout: NSCollectionViewFlowLayout private let dataSource: ApplicationGridDataSource let collectionView: NSCollectionView @@ -137,7 +137,142 @@ struct ApplicationGridViewModel: Hashable { let subtitle: String let application: Application } -class SystemPreferenceViewController: NSViewController { + +class ApplicationListViewController: NSViewController, Component { + private let layout: NSCollectionViewFlowLayout + private let dataSource: ApplicationListDataSource + let collectionView: NSCollectionView + + init(title: String? = nil, + layout: NSCollectionViewFlowLayout, + iconStore: IconStore, + collectionView: NSCollectionView? = nil) { + self.layout = layout + self.dataSource = ApplicationListDataSource(title: title, iconStore: iconStore) + if let collectionView = collectionView { + self.collectionView = collectionView + } else { + self.collectionView = NSCollectionView() + } + self.collectionView.collectionViewLayout = layout + super.init(nibName: nil, bundle: nil) + self.title = title + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View lifecycle + + override func loadView() { + self.view = collectionView + } + + override func viewDidLoad() { + super.viewDidLoad() + collectionView.dataSource = dataSource + let headerIdentifier = NSUserInterfaceItemIdentifier.init("ApplicationListViewHeader") + collectionView.register(CollectionViewHeader.self, + forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader, + withIdentifier: headerIdentifier) + let itemIdentifier = NSUserInterfaceItemIdentifier.init("ApplicationListView") + collectionView.register(ApplicationListView.self, forItemWithIdentifier: itemIdentifier) + + if title != nil { + layout.headerReferenceSize.height = 60 + } + } + + // MARK: - Public API + + func indexPath(for item: NSCollectionViewItem) -> IndexPath? { + return collectionView.indexPath(for: item) + } + + func model(at indexPath: IndexPath) -> ApplicationListViewModel { + return dataSource.model(at: indexPath) + } + + func reload(with models: [ApplicationListViewModel], completion: (() -> Void)? = nil) { + dataSource.reload(collectionView, with: models, then: completion) + } +} + +class ApplicationListDataSource: NSObject, NSCollectionViewDataSource { + + private var title: String? + private var models = [ApplicationListViewModel]() + private let iconStore: IconStore + + init(title: String? = nil, + models: [ApplicationListViewModel] = [], + iconStore: IconStore) { + self.title = title + self.models = models + self.iconStore = iconStore + super.init() + } + + // MARK: - Public API + + func model(at indexPath: IndexPath) -> ApplicationListViewModel { + return models[indexPath.item] + } + + func reload(_ collectionView: NSCollectionView, + with models: [ApplicationListViewModel], + then handler: (() -> Void)? = nil) { + let manager = DiffManager() + let changes = manager.diff(self.models, models) + collectionView.reload(with: changes, + updateDataSource: { self.models = models }, + completion: handler) + } + + // MARK: - NSCollectionViewDataSource + + func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { + return models.count + } + + func collectionView(_ collectionView: NSCollectionView, + viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, + at indexPath: IndexPath) -> NSView { + let identifier = NSUserInterfaceItemIdentifier.init("ApplicationListViewHeader") + let item = collectionView.makeSupplementaryView(ofKind: NSCollectionView.elementKindSectionHeader, + withIdentifier: identifier, for: indexPath) + + if let title = title, let header = item as? CollectionViewHeader { + header.setText(title) + } + + return item + } + + func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { + let identifier = NSUserInterfaceItemIdentifier.init("ApplicationListView") + let item = collectionView.makeItem(withIdentifier: identifier, for: indexPath) + let model = self.model(at: indexPath) + + if let view = item as? ApplicationListView { + view.currentAppearance = model.application.appearance + iconStore.loadIcon(for: model.application) { image in view.iconView.image = image } + view.titleLabel.stringValue = model.title + view.subtitleLabel.stringValue = model.subtitle + } + + return item + } +} + +struct ApplicationListViewModel: Hashable { + let title: String + let subtitle: String + let application: Application +} + +class SystemPreferenceViewController: NSViewController, Component { private let layout: NSCollectionViewFlowLayout private let dataSource: SystemPreferenceDataSource let collectionView: NSCollectionView @@ -270,3 +405,4 @@ struct SystemPreferenceViewModel: Hashable { let subtitle: String let preference: SystemPreference } + diff --git a/Voodoo/Output/ViewControllerFactory-macOS.generated.swift b/Voodoo/Output/ViewControllerFactory-macOS.generated.swift index 9895935..d2ebf61 100644 --- a/Voodoo/Output/ViewControllerFactory-macOS.generated.swift +++ b/Voodoo/Output/ViewControllerFactory-macOS.generated.swift @@ -9,6 +9,10 @@ class ViewControllerFactory { let viewController = ApplicationGridViewController(layout: layout, iconStore: iconStore) return viewController } + public func createApplicationListViewController(layout: NSCollectionViewFlowLayout, iconStore: IconStore) -> ApplicationListViewController { + let viewController = ApplicationListViewController(layout: layout, iconStore: iconStore) + return viewController + } public func createSystemPreferenceViewController(layout: NSCollectionViewFlowLayout, iconStore: IconStore) -> SystemPreferenceViewController { let viewController = SystemPreferenceViewController(layout: layout, iconStore: iconStore) return viewController diff --git a/Voodoo/Templates/CollectionViewItemComponent-macOS.stencil b/Voodoo/Templates/CollectionViewItemComponent-macOS.stencil index 5d29896..4362cee 100644 --- a/Voodoo/Templates/CollectionViewItemComponent-macOS.stencil +++ b/Voodoo/Templates/CollectionViewItemComponent-macOS.stencil @@ -2,7 +2,7 @@ import Cocoa import Differific {% for type in types.implementing.CollectionViewItemComponent %} -class {{type.name|replace:"View",""}}ViewController: NSViewController { +class {{type.name|replace:"View",""}}ViewController: NSViewController, Component { private let layout: NSCollectionViewFlowLayout private let dataSource: {{type.name|replace:"View",""}}DataSource let collectionView: NSCollectionView @@ -155,4 +155,5 @@ struct {{type.name}}Model: Hashable { {{key}}: {{type.annotations[key]}} {% endfor %} } + {% endfor %} From 4c186e98a6ce93c10abfaedf25615266021a0a47 Mon Sep 17 00:00:00 2001 From: Christoffer Winterkvist Date: Sun, 10 Feb 2019 21:07:56 +0100 Subject: [PATCH 2/2] Restore Podfile --- Podfile | 2 +- Podfile.lock | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Podfile b/Podfile index 7cda3dc..f9dc876 100644 --- a/Podfile +++ b/Podfile @@ -4,7 +4,7 @@ platform :macos, '10.14' # Frameworks pod 'Blueprints' pod 'Differific' -pod 'Family', path: '_pods/Family' +pod 'Family' pod 'UserInterface' pod 'Sourcery' diff --git a/Podfile.lock b/Podfile.lock index e919873..c330aa1 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -8,7 +8,7 @@ PODS: DEPENDENCIES: - Blueprints - Differific - - Family (from `_pods/Family`) + - Family - Sourcery - UserInterface @@ -16,13 +16,10 @@ SPEC REPOS: https://github.com/cocoapods/specs.git: - Blueprints - Differific + - Family - Sourcery - UserInterface -EXTERNAL SOURCES: - Family: - :path: _pods/Family - SPEC CHECKSUMS: Blueprints: 6dea8aa1e682d4ee405b83502569011e8640f394 Differific: b26ecab6daa1a2a93efeee41cec5e9593ced09bc @@ -30,6 +27,6 @@ SPEC CHECKSUMS: Sourcery: 5895672cae353cdbfa95f3f4aaeb75a664d76f6a UserInterface: 54e15db9aceaec2b9686d00f471adb15d2598ea3 -PODFILE CHECKSUM: 5860d322941de887dd5e7287667d76345e73097d +PODFILE CHECKSUM: 9a4a4208e8595d7fa51cbc758521284dec9359cc COCOAPODS: 1.6.0