-
-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
29 changed files
with
1,914 additions
and
405 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// | ||
// AppCollection.swift | ||
// Latest | ||
// | ||
// Created by Max Langer on 15.08.18. | ||
// Copyright © 2018 Max Langer. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
/// The collection handling the apps | ||
/// This structure supports the following states: | ||
/// - All apps with updates available | ||
/// - All installed apps, separated from the ones with updates through sections | ||
struct AppCollection { | ||
|
||
/// Holds the apps | ||
fileprivate var data = [AppBundle]() | ||
|
||
/// Flag indicating if all apps are presented | ||
var showInstalledUpdates = false | ||
|
||
/// The indexes of the sections as well as installed apps | ||
var indexesOfInstalledApps: IndexSet { | ||
// The first section header | ||
var indexSet = IndexSet(integer: 0) | ||
|
||
// All installed apps including the second header | ||
indexSet.insert(integersIn: (self.countOfAvailableUpdates + 1)..<(self.data.count + 2)) | ||
|
||
return indexSet | ||
} | ||
|
||
/// The number of apps available | ||
var count: Int { | ||
if !self.showInstalledUpdates { | ||
return self.data.filter({ $0.updateAvailable }).count | ||
} | ||
|
||
return self.data.count + 2 | ||
} | ||
|
||
/// The cached value of the count of apps with updates available | ||
private(set) var countOfAvailableUpdates: Int = 0 | ||
|
||
/// Adds a new app to the collection | ||
mutating func append(_ element: Element) { | ||
self.data.append(element) | ||
self.data.sort { (bundle1, bundle2) -> Bool in | ||
if bundle1.updateAvailable != bundle2.updateAvailable { | ||
return bundle1.updateAvailable | ||
} | ||
|
||
return bundle1.name < bundle2.name | ||
} | ||
|
||
self.updateCountOfAvailableUpdates() | ||
} | ||
|
||
/// Returns the relative index of the element. This index may not reflect the internal position of the app due to section offsets | ||
func index(of element: Element) -> Index? { | ||
guard element.updateAvailable || self.showInstalledUpdates, let index = self.data.firstIndex(of: element) else { return nil } | ||
|
||
return self.align(index) | ||
} | ||
|
||
/// Removes the app from the collection | ||
@discardableResult | ||
mutating func remove(_ appBundle: AppBundle) -> Int? { | ||
guard let index = self.data.firstIndex(where: { $0 == appBundle }) else { return nil } | ||
|
||
self.data.remove(at: index) | ||
self.updateCountOfAvailableUpdates() | ||
|
||
return self.align(index) | ||
} | ||
|
||
/// Returns whether there is a section at the given index | ||
func isSectionHeader(at index: Int) -> Bool { | ||
guard self.showInstalledUpdates else { return false } | ||
|
||
return [0, self.countOfAvailableUpdates + 1].contains(index) | ||
} | ||
|
||
/// This method counts all available updates. It assumes that the array is sorted with all updates at the beginning | ||
private mutating func updateCountOfAvailableUpdates() { | ||
self.countOfAvailableUpdates = self.data.firstIndex(where: { !$0.updateAvailable }) ?? self.data.count | ||
} | ||
|
||
/// Aligns the index based on the section headers | ||
private func align(_ index: Int) -> Int { | ||
var index = index | ||
|
||
if self.showInstalledUpdates { | ||
index += self.countOfAvailableUpdates < index ? 2 : 1 | ||
} | ||
|
||
if self.isSectionHeader(at: index) { | ||
index += 1 | ||
} | ||
|
||
return index | ||
} | ||
|
||
} | ||
|
||
extension AppCollection: Collection { | ||
|
||
typealias DataType = [AppBundle] | ||
|
||
typealias Index = DataType.Index | ||
typealias Element = DataType.Element | ||
typealias Iterator = DataType.Iterator | ||
|
||
var startIndex: Index { return self.data.startIndex } | ||
var endIndex: Index { return self.data.endIndex } | ||
|
||
subscript(position: Index) -> Element { | ||
var position = position | ||
|
||
if self.showInstalledUpdates { | ||
position -= self.countOfAvailableUpdates < position ? 2 : 1 // Remove first row | ||
} | ||
|
||
return self.data[Swift.max(position, 0)] | ||
} | ||
|
||
func makeIterator() -> Iterator { | ||
return self.data.makeIterator() | ||
} | ||
|
||
// Method that returns the next index when iterating | ||
func index(after i: Index) -> Index { | ||
return self.data.index(after: i) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// | ||
// IconCache.swift | ||
// Latest | ||
// | ||
// Created by Max Langer on 12.08.18. | ||
// Copyright © 2018 Max Langer. All rights reserved. | ||
// | ||
|
||
import AppKit | ||
|
||
class IconCache { | ||
|
||
static var shared = IconCache() | ||
|
||
private var cache: NSCache<NSURL, NSImage> | ||
|
||
init() { | ||
self.cache = NSCache() | ||
} | ||
|
||
func icon(for app: AppBundle, with completion: @escaping (NSImage) -> Void) { | ||
if let icon = self.cache.object(forKey: app.url as NSURL) { | ||
completion(icon) | ||
} | ||
|
||
DispatchQueue.main.async { | ||
let icon = NSWorkspace.shared.icon(forFile: app.url.path) | ||
self.cache.setObject(icon, forKey: app.url as NSURL) | ||
|
||
completion(icon) | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.