Added ability to group sorted articles by feed

This commit is contained in:
Phil Viso 2019-09-08 16:48:50 -05:00
parent 1718810701
commit 00e009a82c
5 changed files with 196 additions and 15 deletions

View File

@ -340,6 +340,8 @@
D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; };
DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; };
DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF999FF622B5AEFA0064B687 /* SafariView.swift */; };
FF3ABF13232599810074C542 /* ArticleArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF09232599450074C542 /* ArticleArrayTests.swift */; };
FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -975,6 +977,8 @@
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>"; };
DF999FF622B5AEFA0064B687 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
FF3ABF09232599450074C542 /* ArticleArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleArrayTests.swift; sourceTree = "<group>"; };
FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorter.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -1254,8 +1258,9 @@
isa = PBXGroup;
children = (
84F204DF1FAACBB30076E152 /* ArticleArray.swift */,
84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */,
FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */,
84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */,
84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */,
849A97731ED9EC04007D329B /* TimelineStringFormatter.swift */,
);
path = Timeline;
@ -1877,6 +1882,7 @@
isa = PBXGroup;
children = (
84F9EAD0213660A100CF2DE4 /* ScriptingTests */,
FF3ABF09232599450074C542 /* ArticleArrayTests.swift */,
84F9EAE3213660A100CF2DE4 /* NetNewsWireTests.swift */,
DD82AB09231003F6002269DF /* SharingTests.swift */,
84F9EAE4213660A100CF2DE4 /* Info.plist */,
@ -2608,6 +2614,7 @@
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */,
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */,
FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */,
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */,
849A97791ED9EC04007D329B /* TimelineStringFormatter.swift in Sources */,
84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */,
@ -2686,6 +2693,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FF3ABF13232599810074C542 /* ArticleArrayTests.swift in Sources */,
DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */,
84F9EAEB213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript in Sources */,
84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */,

View File

@ -90,3 +90,21 @@ extension Article {
}
}
// MARK: SortableArticle
extension Article: SortableArticle {
var sortableName: String {
return feed?.name ?? ""
}
var sortableDate: Date {
return logicalDatePublished
}
var sortableID: String {
return articleID
}
}

View File

@ -49,22 +49,11 @@ extension Array where Element == Article {
return articleAtRow(oneIndex)
})
}
func sortedByDate(_ sortDirection: ComparisonResult) -> ArticleArray {
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 sortedByDate(_ sortDirection: ComparisonResult, groupByFeed: Bool = false) -> ArticleArray {
return ArticleSorter.sortedByDate(articles: self, sortDirection: sortDirection, groupByFeed: groupByFeed)
}
func canMarkAllAsRead() -> Bool {
return anyArticleIsUnread()

View File

@ -0,0 +1,57 @@
//
// 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 sortableID: String { get }
}
struct ArticleSorter {
static func sortedByDate<T: SortableArticle>(articles: [T],
sortDirection: ComparisonResult,
groupByFeed: Bool) -> [T] {
let articles = articles.sorted { (article1, article2) -> Bool in
if groupByFeed {
let feedName1 = article1.sortableName
let feedName2 = article2.sortableName
let comparison = feedName1.caseInsensitiveCompare(feedName2)
switch comparison {
case .orderedSame:
return isSortedByDate(article1, article2, sortDirection: sortDirection)
case .orderedAscending, .orderedDescending:
return comparison == .orderedAscending
}
} else {
return isSortedByDate(article1, article2, sortDirection: sortDirection)
}
}
return articles
}
// MARK: -
private static func isSortedByDate(_ lhs: SortableArticle,
_ rhs: SortableArticle,
sortDirection: ComparisonResult) -> Bool {
if lhs.sortableDate == rhs.sortableDate {
return lhs.sortableID < rhs.sortableID
}
if sortDirection == .orderedDescending {
return lhs.sortableDate > rhs.sortableDate
}
return lhs.sortableDate < rhs.sortableDate
}
}

View File

@ -0,0 +1,109 @@
//
// ArticleArrayTests.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 ArticleArrayTests: XCTestCase {
func testSortByDateAscending() {
let now = Date()
// Test data includes a mixture of articles in the past and future, as well as articles with the same date
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableID: "456")
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableID: "789")
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableID: "123")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableID: "345")
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableID: "567")
let article6 = TestArticle(sortableName: "phil's Feed", sortableDate: Date(timeInterval: 60.0, since: now), sortableID: "567")
let articles = [article1, article2, article3, article4, article5, article6]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
sortDirection: .orderedAscending,
groupByFeed: false)
XCTAssertEqual(sortedArticles, [article5, article4, article3, article1, article2, article6])
}
func testSortByDateAscendingWithGroupByFeed() {
let now = Date()
// Test data includes multiple groups (with case-insentive names), articles in the past and future,
// as well as articles with the same date
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -240.0, since: now), sortableID: "123")
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableID: "456")
let article3 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableID: "234")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableID: "123")
let article5 = TestArticle(sortableName: "phil's feed", sortableDate: Date(timeInterval: 60.0, since: now), sortableID: "456")
let article6 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableID: "123")
let article7 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableID: "123")
let article8 = TestArticle(sortableName: "phil's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableID: "456")
let article9 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableID: "345")
let article10 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -15.0, since: now), sortableID: "123")
let article11 = TestArticle(sortableName: "Matt's Feed", sortableDate: Date(timeInterval: 60.0, since: now), sortableID: "123")
let article12 = TestArticle(sortableName: "Claire's Feed", sortableDate: now, sortableID: "123")
let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9, article10, article11, article12]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
sortDirection: .orderedAscending,
groupByFeed: true)
XCTAssertEqual(sortedArticles, [article12, article6, article3, article9, article11, article8, article2, article5, article1, article4, article7, article10])
}
func testSortByDateDescending() {
let now = Date()
// Test data includes a mixture of articles in the past and future, as well as articles with the same date
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableID: "456")
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableID: "789")
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableID: "123")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableID: "345")
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableID: "567")
let article6 = TestArticle(sortableName: "phil's Feed", sortableDate: Date(timeInterval: 60.0, since: now), sortableID: "567")
let articles = [article1, article2, article3, article4, article5, article6]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
sortDirection: .orderedDescending,
groupByFeed: false)
XCTAssertEqual(sortedArticles, [article6, article3, article1, article2, article4, article5])
}
func testSortByDateDescendingWithGroupByFeed() {
let now = Date()
// Test data includes multiple groups (with case-insentive names), articles in the past and future,
// as well as articles with the same date
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -240.0, since: now), sortableID: "123")
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableID: "456")
let article3 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableID: "234")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableID: "123")
let article5 = TestArticle(sortableName: "phil's feed", sortableDate: Date(timeInterval: 60.0, since: now), sortableID: "456")
let article6 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableID: "123")
let article7 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableID: "123")
let article8 = TestArticle(sortableName: "phil's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableID: "456")
let article9 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableID: "345")
let article10 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -15.0, since: now), sortableID: "123")
let article11 = TestArticle(sortableName: "Matt's Feed", sortableDate: Date(timeInterval: 60.0, since: now), sortableID: "123")
let article12 = TestArticle(sortableName: "Claire's Feed", sortableDate: now, sortableID: "123")
let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9, article10, article11, article12]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
sortDirection: .orderedDescending,
groupByFeed: true)
XCTAssertEqual(sortedArticles, [article12, article11, article6, article3, article9, article5, article2, article8, article10, article7, article4, article1])
}
}
private struct TestArticle: SortableArticle, Equatable {
let sortableName: String
let sortableDate: Date
let sortableID: String
}