Merge pull request #1016 from philviso/GroupArticlesByFeed
Add setting to group articles by feed in timeline
This commit is contained in:
commit
1bcd265c80
@ -22,6 +22,7 @@ struct AppDefaults {
|
|||||||
static let sidebarFontSize = "sidebarFontSize"
|
static let sidebarFontSize = "sidebarFontSize"
|
||||||
static let timelineFontSize = "timelineFontSize"
|
static let timelineFontSize = "timelineFontSize"
|
||||||
static let timelineSortDirection = "timelineSortDirection"
|
static let timelineSortDirection = "timelineSortDirection"
|
||||||
|
static let timelineGroupByFeed = "timelineGroupByFeed"
|
||||||
static let detailFontSize = "detailFontSize"
|
static let detailFontSize = "detailFontSize"
|
||||||
static let openInBrowserInBackground = "openInBrowserInBackground"
|
static let openInBrowserInBackground = "openInBrowserInBackground"
|
||||||
static let mainWindowWidths = "mainWindowWidths"
|
static let mainWindowWidths = "mainWindowWidths"
|
||||||
@ -137,6 +138,15 @@ struct AppDefaults {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var timelineGroupByFeed: Bool {
|
||||||
|
get {
|
||||||
|
return bool(for: Key.timelineGroupByFeed)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
setBool(for: Key.timelineGroupByFeed, newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static var timelineShowsSeparators: Bool {
|
static var timelineShowsSeparators: Bool {
|
||||||
return bool(for: Key.timelineShowsSeparators)
|
return bool(for: Key.timelineShowsSeparators)
|
||||||
}
|
}
|
||||||
@ -161,7 +171,13 @@ struct AppDefaults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func registerDefaults() {
|
static func registerDefaults() {
|
||||||
let defaults: [String : Any] = [Key.sidebarFontSize: FontSize.medium.rawValue, Key.timelineFontSize: FontSize.medium.rawValue, Key.detailFontSize: FontSize.medium.rawValue, Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, "NSScrollViewShouldScrollUnderTitlebar": false, Key.refreshInterval: RefreshInterval.everyHour.rawValue]
|
let defaults: [String : Any] = [Key.sidebarFontSize: FontSize.medium.rawValue,
|
||||||
|
Key.timelineFontSize: FontSize.medium.rawValue,
|
||||||
|
Key.detailFontSize: FontSize.medium.rawValue,
|
||||||
|
Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue,
|
||||||
|
Key.timelineGroupByFeed: false,
|
||||||
|
"NSScrollViewShouldScrollUnderTitlebar": false,
|
||||||
|
Key.refreshInterval: RefreshInterval.everyHour.rawValue]
|
||||||
|
|
||||||
UserDefaults.standard.register(defaults: defaults)
|
UserDefaults.standard.register(defaults: defaults)
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||||||
@IBOutlet var debugMenuItem: NSMenuItem!
|
@IBOutlet var debugMenuItem: NSMenuItem!
|
||||||
@IBOutlet var sortByOldestArticleOnTopMenuItem: NSMenuItem!
|
@IBOutlet var sortByOldestArticleOnTopMenuItem: NSMenuItem!
|
||||||
@IBOutlet var sortByNewestArticleOnTopMenuItem: NSMenuItem!
|
@IBOutlet var sortByNewestArticleOnTopMenuItem: NSMenuItem!
|
||||||
|
@IBOutlet var groupArticlesByFeedMenuItem: NSMenuItem!
|
||||||
@IBOutlet var checkForUpdatesMenuItem: NSMenuItem!
|
@IBOutlet var checkForUpdatesMenuItem: NSMenuItem!
|
||||||
|
|
||||||
var unreadCount = 0 {
|
var unreadCount = 0 {
|
||||||
@ -148,6 +149,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||||||
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
|
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
|
||||||
|
|
||||||
updateSortMenuItems()
|
updateSortMenuItems()
|
||||||
|
updateGroupByFeedMenuItem()
|
||||||
createAndShowMainWindow()
|
createAndShowMainWindow()
|
||||||
if isFirstRun {
|
if isFirstRun {
|
||||||
mainWindowController?.window?.center()
|
mainWindowController?.window?.center()
|
||||||
@ -259,6 +261,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||||||
|
|
||||||
@objc func userDefaultsDidChange(_ note: Notification) {
|
@objc func userDefaultsDidChange(_ note: Notification) {
|
||||||
updateSortMenuItems()
|
updateSortMenuItems()
|
||||||
|
updateGroupByFeedMenuItem()
|
||||||
refreshTimer?.update()
|
refreshTimer?.update()
|
||||||
updateDockBadge()
|
updateDockBadge()
|
||||||
}
|
}
|
||||||
@ -509,6 +512,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||||||
|
|
||||||
AppDefaults.timelineSortDirection = .orderedDescending
|
AppDefaults.timelineSortDirection = .orderedDescending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func groupByFeedToggled(_ sender: NSMenuItem) {
|
||||||
|
AppDefaults.timelineGroupByFeed.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Debug Menu
|
// MARK: - Debug Menu
|
||||||
@ -546,6 +554,11 @@ private extension AppDelegate {
|
|||||||
sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off
|
sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off
|
||||||
sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on
|
sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateGroupByFeedMenuItem() {
|
||||||
|
let groupByFeedEnabled = AppDefaults.timelineGroupByFeed
|
||||||
|
groupArticlesByFeedMenuItem.state = groupByFeedEnabled ? .on : .off
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
<deployment identifier="macosx"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14865.1"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Application-->
|
<!--Application-->
|
||||||
@ -330,9 +331,9 @@
|
|||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Sort By" id="nLP-fa-KUi">
|
<menuItem title="Sort Articles By" id="nLP-fa-KUi">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Sort By" id="OlJ-93-6OP">
|
<menu key="submenu" title="Sort Articles By" id="OlJ-93-6OP">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Newest Article on Top" id="TNS-TV-n0U">
|
<menuItem title="Newest Article on Top" id="TNS-TV-n0U">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
@ -349,6 +350,12 @@
|
|||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
|
<menuItem title="Group By Feed" id="Zxm-O6-NRE">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="groupByFeedToggled:" target="Voe-Tx-rLC" id="Wxz-eM-hJN"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="dZt-2W-gxf"/>
|
<menuItem isSeparatorItem="YES" id="dZt-2W-gxf"/>
|
||||||
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
|
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
|
||||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||||
@ -581,6 +588,7 @@
|
|||||||
<connections>
|
<connections>
|
||||||
<outlet property="checkForUpdatesMenuItem" destination="1nF-7O-aKU" id="JmT-jc-DJ8"/>
|
<outlet property="checkForUpdatesMenuItem" destination="1nF-7O-aKU" id="JmT-jc-DJ8"/>
|
||||||
<outlet property="debugMenuItem" destination="UqE-mp-gtV" id="OnR-lr-Zlt"/>
|
<outlet property="debugMenuItem" destination="UqE-mp-gtV" id="OnR-lr-Zlt"/>
|
||||||
|
<outlet property="groupArticlesByFeedMenuItem" destination="Zxm-O6-NRE" id="gwn-VT-2YZ"/>
|
||||||
<outlet property="sortByNewestArticleOnTopMenuItem" destination="TNS-TV-n0U" id="gix-Nd-9k4"/>
|
<outlet property="sortByNewestArticleOnTopMenuItem" destination="TNS-TV-n0U" id="gix-Nd-9k4"/>
|
||||||
<outlet property="sortByOldestArticleOnTopMenuItem" destination="iii-kP-qoF" id="fTe-Tf-EWG"/>
|
<outlet property="sortByOldestArticleOnTopMenuItem" destination="iii-kP-qoF" id="fTe-Tf-EWG"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
@ -126,7 +126,14 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
|||||||
private var sortDirection = AppDefaults.timelineSortDirection {
|
private var sortDirection = AppDefaults.timelineSortDirection {
|
||||||
didSet {
|
didSet {
|
||||||
if sortDirection != oldValue {
|
if sortDirection != oldValue {
|
||||||
sortDirectionDidChange()
|
sortParametersDidChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var groupByFeed = AppDefaults.timelineGroupByFeed {
|
||||||
|
didSet {
|
||||||
|
if groupByFeed != oldValue {
|
||||||
|
sortParametersDidChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -555,6 +562,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
|||||||
|
|
||||||
self.fontSize = AppDefaults.timelineFontSize
|
self.fontSize = AppDefaults.timelineFontSize
|
||||||
self.sortDirection = AppDefaults.timelineSortDirection
|
self.sortDirection = AppDefaults.timelineSortDirection
|
||||||
|
self.groupByFeed = AppDefaults.timelineGroupByFeed
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func appleInterfaceThemeChanged(_ note: Notification) {
|
@objc func appleInterfaceThemeChanged(_ note: Notification) {
|
||||||
@ -876,8 +884,7 @@ private extension TimelineViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortDirectionDidChange() {
|
func sortParametersDidChange() {
|
||||||
|
|
||||||
performBlockAndRestoreSelection {
|
performBlockAndRestoreSelection {
|
||||||
let unsortedArticles = Set(articles)
|
let unsortedArticles = Set(articles)
|
||||||
replaceArticles(with: unsortedArticles)
|
replaceArticles(with: unsortedArticles)
|
||||||
@ -980,7 +987,7 @@ private extension TimelineViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func replaceArticles(with unsortedArticles: Set<Article>) {
|
func replaceArticles(with unsortedArticles: Set<Article>) {
|
||||||
articles = Array(unsortedArticles).sortedByDate(sortDirection)
|
articles = Array(unsortedArticles).sortedByDate(sortDirection, groupByFeed: groupByFeed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnsortedArticlesSync(for representedObjects: [Any]) -> Set<Article> {
|
func fetchUnsortedArticlesSync(for representedObjects: [Any]) -> Set<Article> {
|
||||||
|
@ -366,6 +366,9 @@
|
|||||||
D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; };
|
D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; };
|
||||||
DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; };
|
DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; };
|
||||||
DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF999FF622B5AEFA0064B687 /* SafariView.swift */; };
|
DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF999FF622B5AEFA0064B687 /* SafariView.swift */; };
|
||||||
|
FF3ABF13232599810074C542 /* ArticleSorterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF09232599450074C542 /* ArticleSorterTests.swift */; };
|
||||||
|
FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; };
|
||||||
|
FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -1046,6 +1049,8 @@
|
|||||||
D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = "<group>"; };
|
D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = "<group>"; };
|
||||||
DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = "<group>"; };
|
DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = "<group>"; };
|
||||||
DF999FF622B5AEFA0064B687 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
DF999FF622B5AEFA0064B687 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||||
|
FF3ABF09232599450074C542 /* ArticleSorterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorterTests.swift; sourceTree = "<group>"; };
|
||||||
|
FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorter.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -1354,8 +1359,9 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
84F204DF1FAACBB30076E152 /* ArticleArray.swift */,
|
84F204DF1FAACBB30076E152 /* ArticleArray.swift */,
|
||||||
84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */,
|
FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */,
|
||||||
84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */,
|
84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */,
|
||||||
|
84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */,
|
||||||
849A97731ED9EC04007D329B /* TimelineStringFormatter.swift */,
|
849A97731ED9EC04007D329B /* TimelineStringFormatter.swift */,
|
||||||
);
|
);
|
||||||
path = Timeline;
|
path = Timeline;
|
||||||
@ -1979,6 +1985,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
84F9EAD0213660A100CF2DE4 /* ScriptingTests */,
|
84F9EAD0213660A100CF2DE4 /* ScriptingTests */,
|
||||||
|
FF3ABF09232599450074C542 /* ArticleSorterTests.swift */,
|
||||||
84F9EAE3213660A100CF2DE4 /* NetNewsWireTests.swift */,
|
84F9EAE3213660A100CF2DE4 /* NetNewsWireTests.swift */,
|
||||||
DD82AB09231003F6002269DF /* SharingTests.swift */,
|
DD82AB09231003F6002269DF /* SharingTests.swift */,
|
||||||
84F9EAE4213660A100CF2DE4 /* Info.plist */,
|
84F9EAE4213660A100CF2DE4 /* Info.plist */,
|
||||||
@ -2643,6 +2650,7 @@
|
|||||||
51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */,
|
51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */,
|
||||||
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */,
|
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */,
|
||||||
5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */,
|
5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */,
|
||||||
|
FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */,
|
||||||
51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */,
|
51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */,
|
||||||
51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */,
|
51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */,
|
||||||
51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */,
|
51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */,
|
||||||
@ -2777,6 +2785,7 @@
|
|||||||
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
|
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
|
||||||
8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */,
|
8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */,
|
||||||
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */,
|
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */,
|
||||||
|
FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */,
|
||||||
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */,
|
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */,
|
||||||
849A97791ED9EC04007D329B /* TimelineStringFormatter.swift in Sources */,
|
849A97791ED9EC04007D329B /* TimelineStringFormatter.swift in Sources */,
|
||||||
84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */,
|
84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */,
|
||||||
@ -2855,6 +2864,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
FF3ABF13232599810074C542 /* ArticleSorterTests.swift in Sources */,
|
||||||
DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */,
|
DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */,
|
||||||
84F9EAEB213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript in Sources */,
|
84F9EAEB213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript in Sources */,
|
||||||
84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */,
|
84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */,
|
||||||
|
@ -90,3 +90,25 @@ extension Article {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: SortableArticle
|
||||||
|
|
||||||
|
extension Article: SortableArticle {
|
||||||
|
|
||||||
|
var sortableName: String {
|
||||||
|
return feed?.name ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortableDate: Date {
|
||||||
|
return logicalDatePublished
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortableArticleID: String {
|
||||||
|
return articleID
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortableFeedID: String {
|
||||||
|
return feedID
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -50,19 +50,8 @@ extension Array where Element == Article {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortedByDate(_ sortDirection: ComparisonResult) -> ArticleArray {
|
func sortedByDate(_ sortDirection: ComparisonResult, groupByFeed: Bool = false) -> ArticleArray {
|
||||||
|
return ArticleSorter.sortedByDate(articles: self, sortDirection: sortDirection, groupByFeed: groupByFeed)
|
||||||
let articles = sorted { (article1, article2) -> Bool in
|
|
||||||
if article1.logicalDatePublished == article2.logicalDatePublished {
|
|
||||||
return article1.articleID < article2.articleID
|
|
||||||
}
|
|
||||||
if sortDirection == .orderedDescending {
|
|
||||||
return article1.logicalDatePublished > article2.logicalDatePublished
|
|
||||||
}
|
|
||||||
return article1.logicalDatePublished < article2.logicalDatePublished
|
|
||||||
}
|
|
||||||
|
|
||||||
return articles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func canMarkAllAsRead() -> Bool {
|
func canMarkAllAsRead() -> Bool {
|
||||||
|
61
Shared/Timeline/ArticleSorter.swift
Normal file
61
Shared/Timeline/ArticleSorter.swift
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// ArticleSorter.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Phil Viso on 9/8/19.
|
||||||
|
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Articles
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol SortableArticle {
|
||||||
|
var sortableName: String { get }
|
||||||
|
var sortableDate: Date { get }
|
||||||
|
var sortableArticleID: String { get }
|
||||||
|
var sortableFeedID: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ArticleSorter {
|
||||||
|
|
||||||
|
static func sortedByDate<T: SortableArticle>(articles: [T],
|
||||||
|
sortDirection: ComparisonResult,
|
||||||
|
groupByFeed: Bool) -> [T] {
|
||||||
|
if groupByFeed {
|
||||||
|
return sortedByFeedName(articles: articles, sortByDateDirection: sortDirection)
|
||||||
|
} else {
|
||||||
|
return sortedByDate(articles: articles, sortDirection: sortDirection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
private static func sortedByFeedName<T: SortableArticle>(articles: [T],
|
||||||
|
sortByDateDirection: ComparisonResult) -> [T] {
|
||||||
|
// Group articles by "feed-feedID" - feed ID is used to differentiate between
|
||||||
|
// two feeds that have the same name
|
||||||
|
let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableFeedID)" }
|
||||||
|
return groupedArticles
|
||||||
|
.sorted { $0.key < $1.key }
|
||||||
|
.flatMap { (tuple) -> [T] in
|
||||||
|
let (_, articles) = tuple
|
||||||
|
|
||||||
|
return sortedByDate(articles: articles, sortDirection: sortByDateDirection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func sortedByDate<T: SortableArticle>(articles: [T],
|
||||||
|
sortDirection: ComparisonResult) -> [T] {
|
||||||
|
return articles.sorted { (article1, article2) -> Bool in
|
||||||
|
if article1.sortableDate == article2.sortableDate {
|
||||||
|
return article1.sortableArticleID < article2.sortableArticleID
|
||||||
|
}
|
||||||
|
if sortDirection == .orderedDescending {
|
||||||
|
return article1.sortableDate > article2.sortableDate
|
||||||
|
}
|
||||||
|
|
||||||
|
return article1.sortableDate < article2.sortableDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
240
Tests/NetNewsWireTests/ArticleSorterTests.swift
Normal file
240
Tests/NetNewsWireTests/ArticleSorterTests.swift
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
//
|
||||||
|
// ArticleSorterTests.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Phil Viso on 9/8/19.
|
||||||
|
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Articles
|
||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import NetNewsWire
|
||||||
|
|
||||||
|
class ArticleSorterTests: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: sortByDate ascending tests
|
||||||
|
|
||||||
|
func testSortByDateAscending() {
|
||||||
|
let now = Date()
|
||||||
|
|
||||||
|
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableFeedID: "4")
|
||||||
|
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableFeedID: "6")
|
||||||
|
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableFeedID: "6")
|
||||||
|
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableFeedID: "5")
|
||||||
|
|
||||||
|
let articles = [article1, article2, article3, article4]
|
||||||
|
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||||
|
sortDirection: .orderedAscending,
|
||||||
|
groupByFeed: false)
|
||||||
|
|
||||||
|
XCTAssertEqual(sortedArticles.count, articles.count)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(0), article4)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(1), article1)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(2), article2)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(3), article3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSortByDateAscendingWithSameDate() {
|
||||||
|
let now = Date()
|
||||||
|
|
||||||
|
// Articles with the same date should end up being sorted by their article ID
|
||||||
|
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
|
||||||
|
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
|
||||||
|
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "3")
|
||||||
|
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableFeedID: "4")
|
||||||
|
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableFeedID: "5")
|
||||||
|
|
||||||
|
let articles = [article1, article2, article3, article4, article5]
|
||||||
|
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||||
|
sortDirection: .orderedAscending,
|
||||||
|
groupByFeed: false)
|
||||||
|
|
||||||
|
XCTAssertEqual(sortedArticles.count, articles.count)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(0), article5)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(1), article4)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(2), article1)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(3), article2)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(4), article3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSortByDateAscendingWithGroupByFeed() {
|
||||||
|
let now = Date()
|
||||||
|
|
||||||
|
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableFeedID: "1")
|
||||||
|
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
|
||||||
|
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "2")
|
||||||
|
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableFeedID: "3")
|
||||||
|
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "3")
|
||||||
|
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableFeedID: "2")
|
||||||
|
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
|
||||||
|
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "0")
|
||||||
|
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "0")
|
||||||
|
|
||||||
|
let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9]
|
||||||
|
let sortedArticles = ArticleSorter.sortedByDate(articles: articles, sortDirection: .orderedAscending, groupByFeed: true)
|
||||||
|
|
||||||
|
XCTAssertEqual(sortedArticles.count, 9)
|
||||||
|
|
||||||
|
// Gordy's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(0), article4)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(1), article5)
|
||||||
|
// Jenny's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(2), article3)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(3), article2)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(4), article6)
|
||||||
|
// Phil's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(5), article1)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(6), article7)
|
||||||
|
// Zippy's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(7), article8)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(8), article9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: sortByDate descending tests
|
||||||
|
|
||||||
|
func testSortByDateDescending() {
|
||||||
|
let now = Date()
|
||||||
|
|
||||||
|
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableFeedID: "4")
|
||||||
|
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableFeedID: "6")
|
||||||
|
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableFeedID: "6")
|
||||||
|
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableFeedID: "5")
|
||||||
|
|
||||||
|
let articles = [article1, article2, article3, article4]
|
||||||
|
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||||
|
sortDirection: .orderedDescending,
|
||||||
|
groupByFeed: false)
|
||||||
|
|
||||||
|
XCTAssertEqual(sortedArticles.count, articles.count)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(0), article3)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(1), article2)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(2), article1)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(3), article4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSortByDateDescendingWithSameDate() {
|
||||||
|
let now = Date()
|
||||||
|
|
||||||
|
// Articles with the same date should end up being sorted by their article ID
|
||||||
|
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
|
||||||
|
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
|
||||||
|
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "3")
|
||||||
|
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableFeedID: "4")
|
||||||
|
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableFeedID: "5")
|
||||||
|
|
||||||
|
let articles = [article1, article2, article3, article4, article5]
|
||||||
|
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||||
|
sortDirection: .orderedDescending,
|
||||||
|
groupByFeed: false)
|
||||||
|
|
||||||
|
XCTAssertEqual(sortedArticles.count, articles.count)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(0), article1)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(1), article2)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(2), article3)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(3), article4)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(4), article5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSortByDateDescendingWithGroupByFeed() {
|
||||||
|
let now = Date()
|
||||||
|
|
||||||
|
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableFeedID: "1")
|
||||||
|
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
|
||||||
|
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "2")
|
||||||
|
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableFeedID: "3")
|
||||||
|
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "3")
|
||||||
|
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableFeedID: "2")
|
||||||
|
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
|
||||||
|
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "0")
|
||||||
|
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "0")
|
||||||
|
|
||||||
|
let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9]
|
||||||
|
let sortedArticles = ArticleSorter.sortedByDate(articles: articles, sortDirection: .orderedDescending, groupByFeed: true)
|
||||||
|
|
||||||
|
XCTAssertEqual(sortedArticles.count, 9)
|
||||||
|
|
||||||
|
// Gordy's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(0), article5)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(1), article4)
|
||||||
|
// Jenny's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(2), article6)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(3), article2)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(4), article3)
|
||||||
|
// Phil's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(5), article7)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(6), article1)
|
||||||
|
// Zippy's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(7), article8)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(8), article9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Additional group by feed tests
|
||||||
|
|
||||||
|
func testGroupByFeedWithCaseInsensitiveFeedNames() {
|
||||||
|
let now = Date()
|
||||||
|
|
||||||
|
let article1 = TestArticle(sortableName: "phil's feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
|
||||||
|
let article2 = TestArticle(sortableName: "PhIl's FEed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
|
||||||
|
let article3 = TestArticle(sortableName: "APPLE's feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "2")
|
||||||
|
let article4 = TestArticle(sortableName: "PHIL'S FEED", sortableDate: now, sortableArticleID: "4", sortableFeedID: "1")
|
||||||
|
let article5 = TestArticle(sortableName: "apple's feed", sortableDate: now, sortableArticleID: "5", sortableFeedID: "2")
|
||||||
|
|
||||||
|
let articles = [article1, article2, article3, article4, article5]
|
||||||
|
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||||
|
sortDirection: .orderedAscending,
|
||||||
|
groupByFeed: true)
|
||||||
|
|
||||||
|
XCTAssertEqual(sortedArticles.count, articles.count)
|
||||||
|
|
||||||
|
// Apple's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(0), article3)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(1), article5)
|
||||||
|
// Phil's feed articles
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(2), article1)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(3), article2)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(4), article4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGroupByFeedWithSameFeedNames() {
|
||||||
|
let now = Date()
|
||||||
|
|
||||||
|
// Articles with the same feed name should be sorted by feed ID
|
||||||
|
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
|
||||||
|
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
|
||||||
|
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "1")
|
||||||
|
let article4 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "4", sortableFeedID: "2")
|
||||||
|
let article5 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "5", sortableFeedID: "1")
|
||||||
|
|
||||||
|
let articles = [article1, article2, article3, article4, article5]
|
||||||
|
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||||
|
sortDirection: .orderedAscending,
|
||||||
|
groupByFeed: true)
|
||||||
|
|
||||||
|
XCTAssertEqual(sortedArticles.count, articles.count)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(0), article3)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(1), article5)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(2), article1)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(3), article2)
|
||||||
|
XCTAssertEqual(sortedArticles.articleAtRow(4), article4)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TestArticle: SortableArticle, Equatable {
|
||||||
|
let sortableName: String
|
||||||
|
let sortableDate: Date
|
||||||
|
let sortableArticleID: String
|
||||||
|
let sortableFeedID: String
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Array where Element == TestArticle {
|
||||||
|
func articleAtRow(_ row: Int) -> TestArticle? {
|
||||||
|
if row < 0 || row == NSNotFound || row > count - 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return self[row]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,10 +12,11 @@ struct AppDefaults {
|
|||||||
|
|
||||||
struct Key {
|
struct Key {
|
||||||
static let firstRunDate = "firstRunDate"
|
static let firstRunDate = "firstRunDate"
|
||||||
|
static let timelineGroupByFeed = "timelineGroupByFeed"
|
||||||
|
static let timelineNumberOfLines = "timelineNumberOfLines"
|
||||||
static let timelineSortDirection = "timelineSortDirection"
|
static let timelineSortDirection = "timelineSortDirection"
|
||||||
static let refreshInterval = "refreshInterval"
|
static let refreshInterval = "refreshInterval"
|
||||||
static let lastRefresh = "lastRefresh"
|
static let lastRefresh = "lastRefresh"
|
||||||
static let timelineNumberOfLines = "timelineNumberOfLines"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static let isFirstRun: Bool = {
|
static let isFirstRun: Bool = {
|
||||||
@ -36,6 +37,15 @@ struct AppDefaults {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var timelineGroupByFeed: Bool {
|
||||||
|
get {
|
||||||
|
return bool(for: Key.timelineGroupByFeed)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
setBool(for: Key.timelineGroupByFeed, newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static var timelineSortDirection: ComparisonResult {
|
static var timelineSortDirection: ComparisonResult {
|
||||||
get {
|
get {
|
||||||
return sortDirection(for: Key.timelineSortDirection)
|
return sortDirection(for: Key.timelineSortDirection)
|
||||||
@ -64,7 +74,10 @@ struct AppDefaults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func registerDefaults() {
|
static func registerDefaults() {
|
||||||
let defaults: [String : Any] = [Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, Key.refreshInterval: RefreshInterval.everyHour.rawValue, Key.timelineNumberOfLines: 3]
|
let defaults: [String : Any] = [Key.refreshInterval: RefreshInterval.everyHour.rawValue,
|
||||||
|
Key.timelineGroupByFeed: false,
|
||||||
|
Key.timelineNumberOfLines: 3,
|
||||||
|
Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue]
|
||||||
UserDefaults.standard.register(defaults: defaults)
|
UserDefaults.standard.register(defaults: defaults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||||||
private(set) var sortDirection = AppDefaults.timelineSortDirection {
|
private(set) var sortDirection = AppDefaults.timelineSortDirection {
|
||||||
didSet {
|
didSet {
|
||||||
if sortDirection != oldValue {
|
if sortDirection != oldValue {
|
||||||
sortDirectionDidChange()
|
sortParametersDidChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private(set) var groupByFeed = AppDefaults.timelineGroupByFeed {
|
||||||
|
didSet {
|
||||||
|
if groupByFeed != oldValue {
|
||||||
|
sortParametersDidChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -400,6 +407,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||||||
|
|
||||||
@objc func userDefaultsDidChange(_ note: Notification) {
|
@objc func userDefaultsDidChange(_ note: Notification) {
|
||||||
self.sortDirection = AppDefaults.timelineSortDirection
|
self.sortDirection = AppDefaults.timelineSortDirection
|
||||||
|
self.groupByFeed = AppDefaults.timelineGroupByFeed
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func accountDidDownloadArticles(_ note: Notification) {
|
@objc func accountDidDownloadArticles(_ note: Notification) {
|
||||||
@ -1226,12 +1234,12 @@ private extension SceneCoordinator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortDirectionDidChange() {
|
func sortParametersDidChange() {
|
||||||
replaceArticles(with: Set(articles), animate: true)
|
replaceArticles(with: Set(articles), animate: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func replaceArticles(with unsortedArticles: Set<Article>, animate: Bool) {
|
func replaceArticles(with unsortedArticles: Set<Article>, animate: Bool) {
|
||||||
let sortedArticles = Array(unsortedArticles).sortedByDate(sortDirection)
|
let sortedArticles = Array(unsortedArticles).sortedByDate(sortDirection, groupByFeed: groupByFeed)
|
||||||
|
|
||||||
if articles != sortedArticles {
|
if articles != sortedArticles {
|
||||||
|
|
||||||
|
@ -54,7 +54,10 @@ struct SettingsView : View {
|
|||||||
func buildTimelineSection() -> some View {
|
func buildTimelineSection() -> some View {
|
||||||
Section(header: Text("TIMELINE")) {
|
Section(header: Text("TIMELINE")) {
|
||||||
Toggle(isOn: $viewModel.sortOldestToNewest) {
|
Toggle(isOn: $viewModel.sortOldestToNewest) {
|
||||||
Text("Sort Oldest to Newest")
|
Text("Sort Newest to Oldest")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $viewModel.groupByFeed) {
|
||||||
|
Text("Group By Feed")
|
||||||
}
|
}
|
||||||
Stepper(value: $viewModel.timelineNumberOfLines, in: 2...6) {
|
Stepper(value: $viewModel.timelineNumberOfLines, in: 2...6) {
|
||||||
Text("Number of Text Lines: \(viewModel.timelineNumberOfLines)")
|
Text("Number of Text Lines: \(viewModel.timelineNumberOfLines)")
|
||||||
@ -221,6 +224,16 @@ struct SettingsView : View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var groupByFeed: Bool {
|
||||||
|
get {
|
||||||
|
return AppDefaults.timelineGroupByFeed
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objectWillChange.send()
|
||||||
|
AppDefaults.timelineGroupByFeed = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var timelineNumberOfLines: Int {
|
var timelineNumberOfLines: Int {
|
||||||
get {
|
get {
|
||||||
return AppDefaults.timelineNumberOfLines
|
return AppDefaults.timelineNumberOfLines
|
||||||
|
Loading…
x
Reference in New Issue
Block a user