Merge branch 'mac-release' into main
This commit is contained in:
commit
ea6cece955
@ -731,11 +731,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
public func fetchUnreadArticleIDs(_ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
database.fetchUnreadArticleIDsAsync(webFeedIDs: flattenedWebFeeds().webFeedIDs(), completion: completion)
|
||||
database.fetchUnreadArticleIDsAsync(completion: completion)
|
||||
}
|
||||
|
||||
public func fetchStarredArticleIDs(_ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
database.fetchStarredArticleIDsAsync(webFeedIDs: flattenedWebFeeds().webFeedIDs(), completion: completion)
|
||||
database.fetchStarredArticleIDsAsync(completion: completion)
|
||||
}
|
||||
|
||||
/// Fetch articleIDs for articles that we should have, but don’t. These articles are either (starred) or (newer than the article cutoff date).
|
||||
|
@ -747,14 +747,16 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
container.removeWebFeed(feed)
|
||||
self.refreshProgress.completeTasks(3)
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
self.refreshProgress.completeTasks(4)
|
||||
completion(.success(feed))
|
||||
self.refreshProgress.completeTasks(3)
|
||||
container.removeWebFeed(feed)
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -399,6 +399,7 @@ private extension TwitterFeedProvider {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale.init(identifier: "en_US_POSIX")
|
||||
dateFormatter.dateFormat = Self.dateFormat
|
||||
decoder.dateDecodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
|
@ -77,7 +77,13 @@ public final class WebFeed: Feed, Renamable, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public var name: String?
|
||||
public var name: String? {
|
||||
didSet {
|
||||
if name != oldValue {
|
||||
postDisplayNameDidChangeNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var authors: Set<Author>? {
|
||||
get {
|
||||
|
@ -222,14 +222,14 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Status
|
||||
|
||||
/// Fetch the articleIDs of unread articles in feeds specified by webFeedIDs.
|
||||
public func fetchUnreadArticleIDsAsync(webFeedIDs: Set<String>, completion: @escaping ArticleIDsCompletionBlock) {
|
||||
articlesTable.fetchUnreadArticleIDsAsync(webFeedIDs, completion)
|
||||
/// Fetch the articleIDs of unread articles.
|
||||
public func fetchUnreadArticleIDsAsync(completion: @escaping ArticleIDsCompletionBlock) {
|
||||
articlesTable.fetchUnreadArticleIDsAsync(completion)
|
||||
}
|
||||
|
||||
/// Fetch the articleIDs of starred articles in feeds specified by webFeedIDs.
|
||||
public func fetchStarredArticleIDsAsync(webFeedIDs: Set<String>, completion: @escaping ArticleIDsCompletionBlock) {
|
||||
articlesTable.fetchStarredArticleIDsAsync(webFeedIDs, completion)
|
||||
/// Fetch the articleIDs of starred articles.
|
||||
public func fetchStarredArticleIDsAsync(completion: @escaping ArticleIDsCompletionBlock) {
|
||||
articlesTable.fetchStarredArticleIDsAsync(completion)
|
||||
}
|
||||
|
||||
/// Fetch articleIDs for articles that we should have, but don’t. These articles are either (starred) or (newer than the article cutoff date).
|
||||
|
@ -435,12 +435,12 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Statuses
|
||||
|
||||
func fetchUnreadArticleIDsAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
fetchArticleIDsAsync(.read, false, webFeedIDs, completion)
|
||||
func fetchUnreadArticleIDsAsync(_ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
statusesTable.fetchArticleIDsAsync(.read, false, completion)
|
||||
}
|
||||
|
||||
func fetchStarredArticleIDsAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
fetchArticleIDsAsync(.starred, true, webFeedIDs, completion)
|
||||
func fetchStarredArticleIDsAsync(_ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
statusesTable.fetchArticleIDsAsync(.starred, true, completion)
|
||||
}
|
||||
|
||||
func fetchStarredArticleIDs() throws -> Set<String> {
|
||||
@ -785,46 +785,6 @@ private extension ArticlesTable {
|
||||
return articlesWithResultSet(resultSet, database)
|
||||
}
|
||||
|
||||
func fetchArticleIDsAsync(_ statusKey: ArticleStatus.Key, _ value: Bool, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
guard !webFeedIDs.isEmpty else {
|
||||
completion(.success(Set<String>()))
|
||||
return
|
||||
}
|
||||
|
||||
queue.runInDatabase { databaseResult in
|
||||
|
||||
func makeDatabaseCalls(_ database: FMDatabase) {
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
var sql = "select articleID from articles natural join statuses where feedID in \(placeholders) and \(statusKey.rawValue)="
|
||||
sql += value ? "1" : "0"
|
||||
sql += ";"
|
||||
|
||||
let parameters = Array(webFeedIDs) as [Any]
|
||||
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(Set<String>()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let articleIDs = resultSet.mapToSet{ $0.string(forColumnIndex: 0) }
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(articleIDs))
|
||||
}
|
||||
}
|
||||
|
||||
switch databaseResult {
|
||||
case .success(let database):
|
||||
makeDatabaseCalls(database)
|
||||
case .failure(let databaseError):
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if webFeedIDs.isEmpty {
|
||||
|
@ -102,6 +102,38 @@ final class StatusesTable: DatabaseTable {
|
||||
return try fetchArticleIDs("select articleID from statuses where starred=1;")
|
||||
}
|
||||
|
||||
func fetchArticleIDsAsync(_ statusKey: ArticleStatus.Key, _ value: Bool, _ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
queue.runInDatabase { databaseResult in
|
||||
|
||||
func makeDatabaseCalls(_ database: FMDatabase) {
|
||||
var sql = "select articleID from statuses where \(statusKey.rawValue)="
|
||||
sql += value ? "1" : "0"
|
||||
sql += ";"
|
||||
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: nil) else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(Set<String>()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let articleIDs = resultSet.mapToSet{ $0.string(forColumnIndex: 0) }
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(articleIDs))
|
||||
}
|
||||
}
|
||||
|
||||
switch databaseResult {
|
||||
case .success(let database):
|
||||
makeDatabaseCalls(database)
|
||||
case .failure(let databaseError):
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchArticleIDsForStatusesWithoutArticlesNewerThan(_ cutoffDate: Date, _ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
queue.runInDatabase { databaseResult in
|
||||
|
||||
|
@ -102,6 +102,10 @@ private extension IconView {
|
||||
}
|
||||
|
||||
func rectForImageView() -> NSRect {
|
||||
guard !(iconImage?.isSymbol ?? false) else {
|
||||
return NSMakeRect(0.0, 0.0, bounds.size.width, bounds.size.height)
|
||||
}
|
||||
|
||||
guard let image = iconImage?.image else {
|
||||
return NSRect.zero
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||
}
|
||||
|
||||
if item.action == #selector(nextUnread(_:)) {
|
||||
return canGoToNextUnread()
|
||||
return canGoToNextUnread(wrappingToTop: true)
|
||||
}
|
||||
|
||||
if item.action == #selector(markAllAsRead(_:)) {
|
||||
|
@ -106,7 +106,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
// When the array is the same — same articles, same order —
|
||||
// but some data in some of the articles may have changed.
|
||||
// Just reload visible cells in this case: don’t call reloadData.
|
||||
articleRowMap = [String: Int]()
|
||||
articleRowMap = [String: [Int]]()
|
||||
reloadVisibleCells()
|
||||
return
|
||||
}
|
||||
@ -124,7 +124,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
showFeedNames = .feed
|
||||
}
|
||||
|
||||
articleRowMap = [String: Int]()
|
||||
articleRowMap = [String: [Int]]()
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
@ -141,7 +141,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
private var fetchSerialNumber = 0
|
||||
private let fetchRequestQueue = FetchRequestQueue()
|
||||
private var exceptionArticleFetcher: ArticleFetcher?
|
||||
private var articleRowMap = [String: Int]() // articleID: rowIndex
|
||||
private var articleRowMap = [String: [Int]]() // articleID: rowIndex
|
||||
private var cellAppearance: TimelineCellAppearance!
|
||||
private var cellAppearanceWithIcon: TimelineCellAppearance!
|
||||
private var showFeedNames: TimelineShowFeedName = .none {
|
||||
@ -1057,20 +1057,25 @@ private extension TimelineViewController {
|
||||
restoreSelection(savedSelection)
|
||||
}
|
||||
|
||||
func row(for articleID: String) -> Int? {
|
||||
func rows(for articleID: String) -> [Int]? {
|
||||
updateArticleRowMapIfNeeded()
|
||||
return articleRowMap[articleID]
|
||||
}
|
||||
|
||||
func row(for article: Article) -> Int? {
|
||||
return row(for: article.articleID)
|
||||
func rows(for article: Article) -> [Int]? {
|
||||
return rows(for: article.articleID)
|
||||
}
|
||||
|
||||
func updateArticleRowMap() {
|
||||
var rowMap = [String: Int]()
|
||||
var rowMap = [String: [Int]]()
|
||||
var index = 0
|
||||
articles.forEach { (article) in
|
||||
rowMap[article.articleID] = index
|
||||
if var indexes = rowMap[article.articleID] {
|
||||
indexes.append(index)
|
||||
rowMap[article.articleID] = indexes
|
||||
} else {
|
||||
rowMap[article.articleID] = [index]
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
articleRowMap = rowMap
|
||||
@ -1086,11 +1091,11 @@ private extension TimelineViewController {
|
||||
var indexes = IndexSet()
|
||||
|
||||
articleIDs.forEach { (articleID) in
|
||||
guard let oneIndex = row(for: articleID) else {
|
||||
guard let rowsIndex = rows(for: articleID) else {
|
||||
return
|
||||
}
|
||||
if oneIndex != NSNotFound {
|
||||
indexes.insert(oneIndex)
|
||||
for rowIndex in rowsIndex {
|
||||
indexes.insert(rowIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
enum AddAccountSections: Int, CaseIterable {
|
||||
case local = 0
|
||||
@ -53,11 +54,11 @@ enum AddAccountSections: Int, CaseIterable {
|
||||
case .icloud:
|
||||
return [.cloudKit]
|
||||
case .web:
|
||||
#if DEBUG
|
||||
return [.bazQux, .feedbin, .feedly, .feedWrangler, .inoreader, .newsBlur, .theOldReader]
|
||||
#else
|
||||
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader]
|
||||
#endif
|
||||
if AppDefaults.shared.isDeveloperBuild {
|
||||
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader].filter({ $0.isDeveloperRestricted == false })
|
||||
} else {
|
||||
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader]
|
||||
}
|
||||
case .selfhosted:
|
||||
return [.freshRSS]
|
||||
case .allOrdered:
|
||||
@ -67,12 +68,17 @@ enum AddAccountSections: Int, CaseIterable {
|
||||
AddAccountSections.selfhosted.sectionContent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
struct AddAccountsView: View {
|
||||
|
||||
weak var parent: NSHostingController<AddAccountsView>? // required because presentationMode.dismiss() doesn't work
|
||||
var addAccountDelegate: AccountsPreferencesAddAccountDelegate?
|
||||
private let chunkLimit = 4 // use this to control number of accounts in each web account column
|
||||
@State private var selectedAccount: AccountType = .onMyMac
|
||||
|
||||
init(delegate: AccountsPreferencesAddAccountDelegate?) {
|
||||
@ -157,7 +163,7 @@ struct AddAccountsView: View {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
Text(account.localizedAccountName())
|
||||
}
|
||||
@ -189,7 +195,7 @@ struct AddAccountsView: View {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
|
||||
Text(account.localizedAccountName())
|
||||
@ -207,6 +213,7 @@ struct AddAccountsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var webAccounts: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Web")
|
||||
@ -214,22 +221,28 @@ struct AddAccountsView: View {
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
Picker(selection: $selectedAccount, label: Text(""), content: {
|
||||
ForEach(AddAccountSections.web.sectionContent.filter({ isRestricted($0) != true }), id: \.self, content: { account in
|
||||
|
||||
HStack(alignment: .center) {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
|
||||
Text(account.localizedAccountName())
|
||||
HStack {
|
||||
ForEach(0..<chunkedWebAccounts().count, content: { chunk in
|
||||
VStack {
|
||||
Picker(selection: $selectedAccount, label: Text(""), content: {
|
||||
ForEach(chunkedWebAccounts()[chunk], id: \.self, content: { account in
|
||||
|
||||
HStack(alignment: .center) {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
Text(account.localizedAccountName())
|
||||
}
|
||||
.tag(account)
|
||||
|
||||
})
|
||||
})
|
||||
Spacer()
|
||||
}
|
||||
.tag(account)
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
.offset(x: 7.5, y: 0)
|
||||
|
||||
Text(AddAccountSections.web.sectionFooter).foregroundColor(.gray)
|
||||
@ -252,7 +265,7 @@ struct AddAccountsView: View {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
|
||||
Text(account.localizedAccountName())
|
||||
@ -272,12 +285,10 @@ struct AddAccountsView: View {
|
||||
AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit })
|
||||
}
|
||||
|
||||
private func isRestricted(_ accountType: AccountType) -> Bool {
|
||||
if AppDefaults.shared.isDeveloperBuild && accountType.isDeveloperRestricted {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
private func chunkedWebAccounts() -> [[AccountType]] {
|
||||
AddAccountSections.web.sectionContent.chunked(into: chunkLimit)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -100,6 +100,10 @@ private extension IconView {
|
||||
}
|
||||
|
||||
func rectForImageView() -> NSRect {
|
||||
guard !(iconImage?.isSymbol ?? false) else {
|
||||
return NSMakeRect(0.0, 0.0, bounds.size.width, bounds.size.height)
|
||||
}
|
||||
|
||||
guard let image = iconImage?.image else {
|
||||
return NSRect.zero
|
||||
}
|
||||
|
@ -1,5 +1,22 @@
|
||||
# Mac Release Notes
|
||||
|
||||
### 6.0.1 build 6030 - 1 Apr 2021
|
||||
|
||||
Adjusted layout of the add account sheet so that it fits on smaller monitors
|
||||
Sidebar: properly scale the smart feed icons when sidebar is set to large size in System Preferences
|
||||
|
||||
### 6.0.1b2 build 6029 - 29 Mar 2021
|
||||
|
||||
Twitter: fixed a date parsing bug that could affect people in some locales, which would prevent Twitter feeds from working for them
|
||||
Feeds list: fixed bug where newly added feed would be called Untitled past the time when the app actually knows its name
|
||||
Fixed bug where next-unread command wouldn’t wrap around when you got to the bottom of the Feeds list
|
||||
|
||||
### 6.0.1b1 build 6028 - 28 Mar 2021
|
||||
|
||||
Timeline: fix bug updating article display when an article with the same article ID appears more than once (which can happen when a person has multiple accounts)
|
||||
iCloud: won’t add feeds that aren’t parseable, which fixes an error upon trying to rename one of these feeds
|
||||
Feedbin: fixed a bug with read/unread status syncing
|
||||
|
||||
### 6.0 build 6027 - 26 Mar 2021
|
||||
|
||||
No code changes since 6.0b5
|
||||
|
@ -1,6 +1,6 @@
|
||||
// High Level Settings common to both the Mac application and any extensions we bundle with it
|
||||
MARKETING_VERSION = 6.0
|
||||
CURRENT_PROJECT_VERSION = 6026
|
||||
MARKETING_VERSION = 6.0.1
|
||||
CURRENT_PROJECT_VERSION = 6030
|
||||
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
|
Loading…
x
Reference in New Issue
Block a user