Merge branch 'ios-candidate' into main
This commit is contained in:
commit
cc6449ed2a
@ -53,9 +53,9 @@ public enum AccountType: Int, Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum FetchType {
|
public enum FetchType {
|
||||||
case starred
|
case starred(_: Int? = nil)
|
||||||
case unread
|
case unread(_: Int? = nil)
|
||||||
case today
|
case today(_: Int? = nil)
|
||||||
case folder(Folder, Bool)
|
case folder(Folder, Bool)
|
||||||
case webFeed(WebFeed)
|
case webFeed(WebFeed)
|
||||||
case articleIDs(Set<String>)
|
case articleIDs(Set<String>)
|
||||||
@ -674,12 +674,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
|
|
||||||
public func fetchArticles(_ fetchType: FetchType) throws -> Set<Article> {
|
public func fetchArticles(_ fetchType: FetchType) throws -> Set<Article> {
|
||||||
switch fetchType {
|
switch fetchType {
|
||||||
case .starred:
|
case .starred(let limit):
|
||||||
return try fetchStarredArticles()
|
return try fetchStarredArticles(limit: limit)
|
||||||
case .unread:
|
case .unread(let limit):
|
||||||
return try fetchUnreadArticles()
|
return try fetchUnreadArticles(limit: limit)
|
||||||
case .today:
|
case .today(let limit):
|
||||||
return try fetchTodayArticles()
|
return try fetchTodayArticles(limit: limit)
|
||||||
case .folder(let folder, let readFilter):
|
case .folder(let folder, let readFilter):
|
||||||
if readFilter {
|
if readFilter {
|
||||||
return try fetchUnreadArticles(folder: folder)
|
return try fetchUnreadArticles(folder: folder)
|
||||||
@ -699,12 +699,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
|
|
||||||
public func fetchArticlesAsync(_ fetchType: FetchType, _ completion: @escaping ArticleSetResultBlock) {
|
public func fetchArticlesAsync(_ fetchType: FetchType, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
switch fetchType {
|
switch fetchType {
|
||||||
case .starred:
|
case .starred(let limit):
|
||||||
fetchStarredArticlesAsync(completion)
|
fetchStarredArticlesAsync(limit: limit, completion)
|
||||||
case .unread:
|
case .unread(let limit):
|
||||||
fetchUnreadArticlesAsync(completion)
|
fetchUnreadArticlesAsync(limit: limit, completion)
|
||||||
case .today:
|
case .today(let limit):
|
||||||
fetchTodayArticlesAsync(completion)
|
fetchTodayArticlesAsync(limit: limit, completion)
|
||||||
case .folder(let folder, let readFilter):
|
case .folder(let folder, let readFilter):
|
||||||
if readFilter {
|
if readFilter {
|
||||||
return fetchUnreadArticlesAsync(folder: folder, completion)
|
return fetchUnreadArticlesAsync(folder: folder, completion)
|
||||||
@ -1046,28 +1046,28 @@ extension Account: WebFeedMetadataDelegate {
|
|||||||
|
|
||||||
private extension Account {
|
private extension Account {
|
||||||
|
|
||||||
func fetchStarredArticles() throws -> Set<Article> {
|
func fetchStarredArticles(limit: Int?) throws -> Set<Article> {
|
||||||
return try database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs())
|
return try database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs(), limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchStarredArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
func fetchStarredArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), completion)
|
database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), limit, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticles() throws -> Set<Article> {
|
func fetchUnreadArticles(limit: Int?) throws -> Set<Article> {
|
||||||
return try fetchUnreadArticles(forContainer: self)
|
return try fetchUnreadArticles(forContainer: self, limit: limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
func fetchUnreadArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
fetchUnreadArticlesAsync(forContainer: self, completion)
|
fetchUnreadArticlesAsync(forContainer: self, limit: limit, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchTodayArticles() throws -> Set<Article> {
|
func fetchTodayArticles(limit: Int?) throws -> Set<Article> {
|
||||||
return try database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs())
|
return try database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs(), limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchTodayArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
func fetchTodayArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), completion)
|
database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), limit, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchArticles(folder: Folder) throws -> Set<Article> {
|
func fetchArticles(folder: Folder) throws -> Set<Article> {
|
||||||
@ -1079,11 +1079,11 @@ private extension Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticles(folder: Folder) throws -> Set<Article> {
|
func fetchUnreadArticles(folder: Folder) throws -> Set<Article> {
|
||||||
return try fetchUnreadArticles(forContainer: folder)
|
return try fetchUnreadArticles(forContainer: folder, limit: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticlesAsync(folder: Folder, _ completion: @escaping ArticleSetResultBlock) {
|
func fetchUnreadArticlesAsync(folder: Folder, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
fetchUnreadArticlesAsync(forContainer: folder, completion)
|
fetchUnreadArticlesAsync(forContainer: folder, limit: nil, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchArticles(webFeed: WebFeed) throws -> Set<Article> {
|
func fetchArticles(webFeed: WebFeed) throws -> Set<Article> {
|
||||||
@ -1129,7 +1129,7 @@ private extension Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticles(webFeed: WebFeed) throws -> Set<Article> {
|
func fetchUnreadArticles(webFeed: WebFeed) throws -> Set<Article> {
|
||||||
let articles = try database.fetchUnreadArticles(Set([webFeed.webFeedID]))
|
let articles = try database.fetchUnreadArticles(Set([webFeed.webFeedID]), nil)
|
||||||
validateUnreadCount(webFeed, articles)
|
validateUnreadCount(webFeed, articles)
|
||||||
return articles
|
return articles
|
||||||
}
|
}
|
||||||
@ -1154,19 +1154,31 @@ private extension Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticles(forContainer container: Container) throws -> Set<Article> {
|
func fetchUnreadArticles(forContainer container: Container, limit: Int?) throws -> Set<Article> {
|
||||||
let feeds = container.flattenedWebFeeds()
|
let feeds = container.flattenedWebFeeds()
|
||||||
let articles = try database.fetchUnreadArticles(feeds.webFeedIDs())
|
let articles = try database.fetchUnreadArticles(feeds.webFeedIDs(), limit)
|
||||||
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
|
||||||
|
// We don't validate limit queries because they, by definition, won't correctly match the
|
||||||
|
// complete unread state for the given container.
|
||||||
|
if limit == nil {
|
||||||
|
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||||
|
}
|
||||||
|
|
||||||
return articles
|
return articles
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) {
|
func fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
let webFeeds = container.flattenedWebFeeds()
|
let webFeeds = container.flattenedWebFeeds()
|
||||||
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articleSetResult) in
|
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs(), limit) { [weak self] (articleSetResult) in
|
||||||
switch articleSetResult {
|
switch articleSetResult {
|
||||||
case .success(let articles):
|
case .success(let articles):
|
||||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
|
|
||||||
|
// We don't validate limit queries because they, by definition, won't correctly match the
|
||||||
|
// complete unread state for the given container.
|
||||||
|
if limit == nil {
|
||||||
|
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
|
||||||
|
}
|
||||||
|
|
||||||
completion(.success(articles))
|
completion(.success(articles))
|
||||||
case .failure(let databaseError):
|
case .failure(let databaseError):
|
||||||
completion(.failure(databaseError))
|
completion(.failure(databaseError))
|
||||||
|
@ -102,16 +102,16 @@ public final class ArticlesDatabase {
|
|||||||
return try articlesTable.fetchArticles(articleIDs: articleIDs)
|
return try articlesTable.fetchArticles(articleIDs: articleIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchUnreadArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
public func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||||
return try articlesTable.fetchUnreadArticles(webFeedIDs)
|
return try articlesTable.fetchUnreadArticles(webFeedIDs, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchTodayArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
public func fetchTodayArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||||
return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate())
|
return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate(), limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchStarredArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
public func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||||
return try articlesTable.fetchStarredArticles(webFeedIDs)
|
return try articlesTable.fetchStarredArticles(webFeedIDs, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
|
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||||
@ -136,16 +136,16 @@ public final class ArticlesDatabase {
|
|||||||
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, completion)
|
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
articlesTable.fetchUnreadArticlesAsync(webFeedIDs, completion)
|
articlesTable.fetchUnreadArticlesAsync(webFeedIDs, limit, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), completion)
|
articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), limit, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
articlesTable.fetchStarredArticlesAsync(webFeedIDs, completion)
|
articlesTable.fetchStarredArticlesAsync(webFeedIDs, limit, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
|
@ -75,32 +75,32 @@ final class ArticlesTable: DatabaseTable {
|
|||||||
|
|
||||||
// MARK: - Fetching Unread Articles
|
// MARK: - Fetching Unread Articles
|
||||||
|
|
||||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||||
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, $0) }
|
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, limit, $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, $0) }, completion)
|
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, limit, $0) }, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Fetching Today Articles
|
// MARK: - Fetching Today Articles
|
||||||
|
|
||||||
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date) throws -> Set<Article> {
|
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?) throws -> Set<Article> {
|
||||||
return try fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }
|
return try fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, limit, $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ completion: @escaping ArticleSetResultBlock) {
|
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }, completion)
|
fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, limit, $0) }, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Fetching Starred Articles
|
// MARK: - Fetching Starred Articles
|
||||||
|
|
||||||
func fetchStarredArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||||
return try fetchArticles{ self.fetchStarredArticles(webFeedIDs, $0) }
|
return try fetchArticles{ self.fetchStarredArticles(webFeedIDs, limit, $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||||
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, $0) }, completion)
|
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, limit, $0) }, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Fetching Search Articles
|
// MARK: - Fetching Search Articles
|
||||||
@ -796,14 +796,17 @@ private extension ArticlesTable {
|
|||||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
|
||||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||||
if webFeedIDs.isEmpty {
|
if webFeedIDs.isEmpty {
|
||||||
return Set<Article>()
|
return Set<Article>()
|
||||||
}
|
}
|
||||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||||
let whereClause = "feedID in \(placeholders) and read=0"
|
var whereClause = "feedID in \(placeholders) and read=0"
|
||||||
|
if let limit = limit {
|
||||||
|
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
|
||||||
|
}
|
||||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -821,7 +824,7 @@ private extension ArticlesTable {
|
|||||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ database: FMDatabase) -> Set<Article> {
|
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
|
||||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?)
|
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?)
|
||||||
//
|
//
|
||||||
// datePublished may be nil, so we fall back to dateArrived.
|
// datePublished may be nil, so we fall back to dateArrived.
|
||||||
@ -830,18 +833,24 @@ private extension ArticlesTable {
|
|||||||
}
|
}
|
||||||
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
|
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
|
||||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||||
let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?))"
|
var whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?))"
|
||||||
|
if let limit = limit {
|
||||||
|
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
|
||||||
|
}
|
||||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
|
||||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
|
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
|
||||||
if webFeedIDs.isEmpty {
|
if webFeedIDs.isEmpty {
|
||||||
return Set<Article>()
|
return Set<Article>()
|
||||||
}
|
}
|
||||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||||
let whereClause = "feedID in \(placeholders) and starred=1"
|
var whereClause = "feedID in \(placeholders) and starred=1"
|
||||||
|
if let limit = limit {
|
||||||
|
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
|
||||||
|
}
|
||||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,47 +63,81 @@ final class IconImage {
|
|||||||
fileprivate enum ImageLuminanceType {
|
fileprivate enum ImageLuminanceType {
|
||||||
case regular, bright, dark
|
case regular, bright, dark
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CGImage {
|
extension CGImage {
|
||||||
|
|
||||||
func isBright() -> Bool {
|
func isBright() -> Bool {
|
||||||
guard let imageData = self.dataProvider?.data, let luminanceType = getLuminanceType(from: imageData) else {
|
guard let luminanceType = getLuminanceType() else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return luminanceType == .bright
|
return luminanceType == .bright
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDark() -> Bool {
|
func isDark() -> Bool {
|
||||||
guard let imageData = self.dataProvider?.data, let luminanceType = getLuminanceType(from: imageData) else {
|
guard let luminanceType = getLuminanceType() else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return luminanceType == .dark
|
return luminanceType == .dark
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func getLuminanceType(from data: CFData) -> ImageLuminanceType? {
|
fileprivate func getLuminanceType() -> ImageLuminanceType? {
|
||||||
guard let ptr = CFDataGetBytePtr(data) else {
|
|
||||||
return nil
|
// This has been rewritten with information from https://christianselig.com/2021/04/efficient-average-color/
|
||||||
}
|
|
||||||
|
// First, resize the image. We do this for two reasons, 1) less pixels to deal with means faster
|
||||||
let length = CFDataGetLength(data)
|
// calculation and a resized image still has the "gist" of the colors, and 2) the image we're dealing
|
||||||
var pixelCount = 0
|
// with may come in any of a variety of color formats (CMYK, ARGB, RGBA, etc.) which complicates things,
|
||||||
|
// and redrawing it normalizes that into a base color format we can deal with.
|
||||||
|
// 40x40 is a good size to resize to still preserve quite a bit of detail but not have too many pixels
|
||||||
|
// to deal with. Aspect ratio is irrelevant for just finding average color.
|
||||||
|
let size = CGSize(width: 40, height: 40)
|
||||||
|
|
||||||
|
let width = Int(size.width)
|
||||||
|
let height = Int(size.height)
|
||||||
|
let totalPixels = width * height
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
|
||||||
|
// ARGB format
|
||||||
|
let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue
|
||||||
|
|
||||||
|
// 8 bits for each color channel, we're doing ARGB so 32 bits (4 bytes) total, and thus if the image is n pixels wide,
|
||||||
|
// and has 4 bytes per pixel, the total bytes per row is 4n. That gives us 2^8 = 256 color variations for each RGB channel
|
||||||
|
// or 256 * 256 * 256 = ~16.7M color options in total. That seems like a lot, but lots of HDR movies are in 10 bit, which
|
||||||
|
// is (2^10)^3 = 1 billion color options!
|
||||||
|
guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: colorSpace, bitmapInfo: bitmapInfo) else { return nil }
|
||||||
|
|
||||||
|
// Draw our resized image
|
||||||
|
context.draw(self, in: CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
guard let pixelBuffer = context.data else { return nil }
|
||||||
|
|
||||||
|
// Bind the pixel buffer's memory location to a pointer we can use/access
|
||||||
|
let pointer = pixelBuffer.bindMemory(to: UInt32.self, capacity: width * height)
|
||||||
|
|
||||||
var totalLuminance = 0.0
|
var totalLuminance = 0.0
|
||||||
|
|
||||||
for i in stride(from: 0, to: length, by: 4) {
|
// Column of pixels in image
|
||||||
|
for x in 0 ..< width {
|
||||||
let r = ptr[i]
|
// Row of pixels in image
|
||||||
let g = ptr[i + 1]
|
for y in 0 ..< height {
|
||||||
let b = ptr[i + 2]
|
// To get the pixel location just think of the image as a grid of pixels, but stored as one long row
|
||||||
let a = ptr[i + 3]
|
// rather than columns and rows, so for instance to map the pixel from the grid in the 15th row and 3
|
||||||
let luminance = (0.299 * Double(r) + 0.587 * Double(g) + 0.114 * Double(b))
|
// columns in to our "long row", we'd offset ourselves 15 times the width in pixels of the image, and
|
||||||
|
// then offset by the amount of columns
|
||||||
if Double(a) > 0 {
|
let pixel = pointer[(y * width) + x]
|
||||||
|
|
||||||
|
let r = red(for: pixel)
|
||||||
|
let g = green(for: pixel)
|
||||||
|
let b = blue(for: pixel)
|
||||||
|
|
||||||
|
let luminance = (0.299 * Double(r) + 0.587 * Double(g) + 0.114 * Double(b))
|
||||||
|
|
||||||
totalLuminance += luminance
|
totalLuminance += luminance
|
||||||
pixelCount += 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let avgLuminance = totalLuminance / Double(pixelCount)
|
let avgLuminance = totalLuminance / Double(totalPixels)
|
||||||
if totalLuminance == 0 || avgLuminance < 40 {
|
if totalLuminance == 0 || avgLuminance < 40 {
|
||||||
return .dark
|
return .dark
|
||||||
} else if avgLuminance > 180 {
|
} else if avgLuminance > 180 {
|
||||||
@ -113,6 +147,18 @@ extension CGImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func red(for pixelData: UInt32) -> UInt8 {
|
||||||
|
return UInt8((pixelData >> 16) & 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func green(for pixelData: UInt32) -> UInt8 {
|
||||||
|
return UInt8((pixelData >> 8) & 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func blue(for pixelData: UInt32) -> UInt8 {
|
||||||
|
return UInt8((pixelData >> 0) & 255)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ struct StarredFeedDelegate: SmartFeedDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title")
|
let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title")
|
||||||
let fetchType: FetchType = .starred
|
let fetchType: FetchType = .starred(nil)
|
||||||
var smallIcon: IconImage? {
|
var smallIcon: IconImage? {
|
||||||
return AppAssets.starredFeedImage
|
return AppAssets.starredFeedImage
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ struct TodayFeedDelegate: SmartFeedDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title")
|
let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title")
|
||||||
let fetchType = FetchType.today
|
let fetchType = FetchType.today(nil)
|
||||||
var smallIcon: IconImage? {
|
var smallIcon: IconImage? {
|
||||||
return AppAssets.todayFeedImage
|
return AppAssets.todayFeedImage
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ final class UnreadFeed: PseudoFeed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title")
|
let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title")
|
||||||
let fetchType = FetchType.unread
|
let fetchType = FetchType.unread(nil)
|
||||||
|
|
||||||
var unreadCount = 0 {
|
var unreadCount = 0 {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -12,11 +12,13 @@ import os.log
|
|||||||
import UIKit
|
import UIKit
|
||||||
import RSCore
|
import RSCore
|
||||||
import Articles
|
import Articles
|
||||||
|
import Account
|
||||||
|
|
||||||
|
|
||||||
public final class WidgetDataEncoder {
|
public final class WidgetDataEncoder {
|
||||||
|
|
||||||
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
||||||
|
private let fetchLimit = 7
|
||||||
|
|
||||||
private var backgroundTaskID: UIBackgroundTaskIdentifier!
|
private var backgroundTaskID: UIBackgroundTaskIdentifier!
|
||||||
private lazy var appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
|
private lazy var appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
|
||||||
@ -31,11 +33,9 @@ public final class WidgetDataEncoder {
|
|||||||
os_log(.debug, log: log, "Starting encoding widget data.")
|
os_log(.debug, log: log, "Starting encoding widget data.")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let unreadArticles = Array(try SmartFeedsController.shared.unreadFeed.fetchArticles()).sortedByDate(.orderedDescending)
|
let unreadArticles = Array(try AccountManager.shared.fetchArticles(.unread(fetchLimit))).sortedByDate(.orderedDescending)
|
||||||
|
let starredArticles = Array(try AccountManager.shared.fetchArticles(.starred(fetchLimit))).sortedByDate(.orderedDescending)
|
||||||
let starredArticles = Array(try SmartFeedsController.shared.starredFeed.fetchArticles()).sortedByDate(.orderedDescending)
|
let todayArticles = Array(try AccountManager.shared.fetchArticles(.today(fetchLimit))).sortedByDate(.orderedDescending)
|
||||||
|
|
||||||
let todayArticles = Array(try SmartFeedsController.shared.todayFeed.fetchUnreadArticles()).sortedByDate(.orderedDescending)
|
|
||||||
|
|
||||||
var unread = [LatestArticle]()
|
var unread = [LatestArticle]()
|
||||||
var today = [LatestArticle]()
|
var today = [LatestArticle]()
|
||||||
@ -47,9 +47,8 @@ public final class WidgetDataEncoder {
|
|||||||
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
|
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
|
||||||
articleSummary: article.summary,
|
articleSummary: article.summary,
|
||||||
feedIcon: article.iconImage()?.image.dataRepresentation(),
|
feedIcon: article.iconImage()?.image.dataRepresentation(),
|
||||||
pubDate: article.datePublished!.description)
|
pubDate: article.datePublished?.description ?? "")
|
||||||
unread.append(latestArticle)
|
unread.append(latestArticle)
|
||||||
if unread.count == 7 { break }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for article in starredArticles {
|
for article in starredArticles {
|
||||||
@ -58,9 +57,8 @@ public final class WidgetDataEncoder {
|
|||||||
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
|
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
|
||||||
articleSummary: article.summary,
|
articleSummary: article.summary,
|
||||||
feedIcon: article.iconImage()?.image.dataRepresentation(),
|
feedIcon: article.iconImage()?.image.dataRepresentation(),
|
||||||
pubDate: article.datePublished!.description)
|
pubDate: article.datePublished?.description ?? "")
|
||||||
starred.append(latestArticle)
|
starred.append(latestArticle)
|
||||||
if starred.count == 7 { break }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for article in todayArticles {
|
for article in todayArticles {
|
||||||
@ -69,13 +67,12 @@ public final class WidgetDataEncoder {
|
|||||||
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
|
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
|
||||||
articleSummary: article.summary,
|
articleSummary: article.summary,
|
||||||
feedIcon: article.iconImage()?.image.dataRepresentation(),
|
feedIcon: article.iconImage()?.image.dataRepresentation(),
|
||||||
pubDate: article.datePublished!.description)
|
pubDate: article.datePublished?.description ?? "")
|
||||||
today.append(latestArticle)
|
today.append(latestArticle)
|
||||||
if today.count == 7 { break }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
|
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
|
||||||
currentTodayCount: try! SmartFeedsController.shared.todayFeed.fetchUnreadArticles().count,
|
currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount,
|
||||||
currentStarredCount: try! SmartFeedsController.shared.starredFeed.fetchArticles().count,
|
currentStarredCount: try! SmartFeedsController.shared.starredFeed.fetchArticles().count,
|
||||||
unreadArticles: unread,
|
unreadArticles: unread,
|
||||||
starredArticles: starred,
|
starredArticles: starred,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17147" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="fak-6k-FqE">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="fak-6k-FqE">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17120"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="SFq-R0-gSo">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="SFq-R0-gSo">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
|
||||||
<sections>
|
<sections>
|
||||||
<tableViewSection id="Dp6-La-NeL">
|
<tableViewSection id="Dp6-La-NeL">
|
||||||
<cells>
|
<cells>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="The best posts on Reddit for you" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="uaZ-4Q-FBS">
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="The best posts on Reddit for you" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="uaZ-4Q-FBS">
|
||||||
<rect key="frame" x="20" y="32.5" width="198" height="16"/>
|
<rect key="frame" x="20" y="32.5" width="197.5" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
@ -143,7 +143,7 @@
|
|||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="T93-wO-GIE">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="T93-wO-GIE">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
|
||||||
<prototypes>
|
<prototypes>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="j8c-JM-nzm" style="IBUITableViewCellStyleDefault" id="vEE-Gx-Zgc" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="j8c-JM-nzm" style="IBUITableViewCellStyleDefault" id="vEE-Gx-Zgc" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||||
<rect key="frame" x="20" y="55.5" width="374" height="43.5"/>
|
<rect key="frame" x="20" y="55.5" width="374" height="43.5"/>
|
||||||
@ -181,7 +181,7 @@
|
|||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="76O-el-2DO">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="76O-el-2DO">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
|
||||||
<sections>
|
<sections>
|
||||||
<tableViewSection id="ZkR-cP-Kvy">
|
<tableViewSection id="ZkR-cP-Kvy">
|
||||||
<cells>
|
<cells>
|
||||||
@ -229,7 +229,7 @@
|
|||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="fZZ-h8-KOR">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="fZZ-h8-KOR">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
|
||||||
<sections>
|
<sections>
|
||||||
<tableViewSection id="ExH-4T-drs">
|
<tableViewSection id="ExH-4T-drs">
|
||||||
<cells>
|
<cells>
|
||||||
@ -272,7 +272,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="The most upvotes recently" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="l2u-CB-A9e">
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="The most upvotes recently" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="l2u-CB-A9e">
|
||||||
<rect key="frame" x="20" y="32.5" width="161.5" height="16"/>
|
<rect key="frame" x="20" y="32.5" width="161" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
@ -344,7 +344,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Posts getting the most current activity" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="6AI-Dt-0ix">
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Posts getting the most current activity" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="6AI-Dt-0ix">
|
||||||
<rect key="frame" x="20" y="32.5" width="232.5" height="16"/>
|
<rect key="frame" x="20" y="32.5" width="232" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
@ -385,8 +385,8 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<systemColor name="systemBackgroundColor">
|
<systemColor name="systemGroupedBackgroundColor">
|
||||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</systemColor>
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17147" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="4Q4-Hi-Lic">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="4Q4-Hi-Lic">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17120"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="SFq-R0-gSo">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="SFq-R0-gSo">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
|
||||||
<sections>
|
<sections>
|
||||||
<tableViewSection id="Dp6-La-NeL">
|
<tableViewSection id="Dp6-La-NeL">
|
||||||
<cells>
|
<cells>
|
||||||
@ -159,7 +159,7 @@
|
|||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="T93-wO-GIE">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="T93-wO-GIE">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
|
||||||
<prototypes>
|
<prototypes>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="j8c-JM-nzm" style="IBUITableViewCellStyleDefault" id="vEE-Gx-Zgc" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="j8c-JM-nzm" style="IBUITableViewCellStyleDefault" id="vEE-Gx-Zgc" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||||
<rect key="frame" x="20" y="55.5" width="374" height="43.5"/>
|
<rect key="frame" x="20" y="55.5" width="374" height="43.5"/>
|
||||||
@ -197,7 +197,7 @@
|
|||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="76O-el-2DO">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="76O-el-2DO">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
|
||||||
<sections>
|
<sections>
|
||||||
<tableViewSection id="ZkR-cP-Kvy">
|
<tableViewSection id="ZkR-cP-Kvy">
|
||||||
<cells>
|
<cells>
|
||||||
@ -240,8 +240,8 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<systemColor name="systemBackgroundColor">
|
<systemColor name="systemGroupedBackgroundColor">
|
||||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</systemColor>
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
@ -101,6 +101,14 @@ struct AppAssets {
|
|||||||
return UIImage(named: "disclosure")!
|
return UIImage(named: "disclosure")!
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
static var contextMenuReddit: UIImage = {
|
||||||
|
return UIImage(named: "contextMenuReddit")!
|
||||||
|
}()
|
||||||
|
|
||||||
|
static var contextMenuTwitter: UIImage = {
|
||||||
|
return UIImage(named: "contextMenuTwitter")!
|
||||||
|
}()
|
||||||
|
|
||||||
static var copyImage: UIImage = {
|
static var copyImage: UIImage = {
|
||||||
return UIImage(systemName: "doc.on.doc")!
|
return UIImage(systemName: "doc.on.doc")!
|
||||||
}()
|
}()
|
||||||
@ -133,6 +141,10 @@ struct AppAssets {
|
|||||||
UIImage(systemName: "line.horizontal.3.decrease.circle.fill")!
|
UIImage(systemName: "line.horizontal.3.decrease.circle.fill")!
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
static var folderOutlinePlus: UIImage = {
|
||||||
|
UIImage(systemName: "folder.badge.plus")!
|
||||||
|
}()
|
||||||
|
|
||||||
static var fullScreenBackgroundColor: UIColor = {
|
static var fullScreenBackgroundColor: UIColor = {
|
||||||
return UIColor(named: "fullScreenBackgroundColor")!
|
return UIColor(named: "fullScreenBackgroundColor")!
|
||||||
}()
|
}()
|
||||||
@ -173,6 +185,10 @@ struct AppAssets {
|
|||||||
return UIImage(systemName: "chevron.down.circle")!
|
return UIImage(systemName: "chevron.down.circle")!
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
static var plus: UIImage = {
|
||||||
|
UIImage(systemName: "plus")!
|
||||||
|
}()
|
||||||
|
|
||||||
static var prevArticleImage: UIImage = {
|
static var prevArticleImage: UIImage = {
|
||||||
return UIImage(systemName: "chevron.up")!
|
return UIImage(systemName: "chevron.up")!
|
||||||
}()
|
}()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// OpenInSafariActivity.swift
|
// OpenInBrowserActivity.swift
|
||||||
// NetNewsWire-iOS
|
// NetNewsWire-iOS
|
||||||
//
|
//
|
||||||
// Created by Maurice Parker on 1/9/20.
|
// Created by Maurice Parker on 1/9/20.
|
||||||
@ -8,16 +8,16 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class OpenInSafariActivity: UIActivity {
|
class OpenInBrowserActivity: UIActivity {
|
||||||
|
|
||||||
private var activityItems: [Any]?
|
private var activityItems: [Any]?
|
||||||
|
|
||||||
override var activityTitle: String? {
|
override var activityTitle: String? {
|
||||||
return NSLocalizedString("Open in Safari", comment: "Open in Safari")
|
return NSLocalizedString("Open in Browser", comment: "Open in Browser")
|
||||||
}
|
}
|
||||||
|
|
||||||
override var activityImage: UIImage? {
|
override var activityImage: UIImage? {
|
||||||
return UIImage(systemName: "safari", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))
|
return UIImage(systemName: "globe", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))
|
||||||
}
|
}
|
||||||
|
|
||||||
override var activityType: UIActivity.ActivityType? {
|
override var activityType: UIActivity.ActivityType? {
|
||||||
|
@ -239,7 +239,7 @@ class WebViewController: UIViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let activityViewController = UIActivityViewController(url: url, title: article?.title, applicationActivities: [FindInArticleActivity(), OpenInSafariActivity()])
|
let activityViewController = UIActivityViewController(url: url, title: article?.title, applicationActivities: [FindInArticleActivity(), OpenInBrowserActivity()])
|
||||||
activityViewController.popoverPresentationController?.barButtonItem = popOverBarButtonItem
|
activityViewController.popoverPresentationController?.barButtonItem = popOverBarButtonItem
|
||||||
present(activityViewController, animated: true)
|
present(activityViewController, animated: true)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view hidden="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="h1Q-FS-jlg" customClass="ArticleSearchBar" customModule="NetNewsWire" customModuleProvider="target">
|
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="h1Q-FS-jlg" customClass="ArticleSearchBar" customModule="NetNewsWire" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="777" width="414" height="36"/>
|
<rect key="frame" x="0.0" y="777" width="414" height="36"/>
|
||||||
<color key="backgroundColor" name="barBackgroundColor"/>
|
<color key="backgroundColor" name="barBackgroundColor"/>
|
||||||
</view>
|
</view>
|
||||||
@ -138,6 +138,9 @@
|
|||||||
<userDefinedRuntimeAttributes>
|
<userDefinedRuntimeAttributes>
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="accLabelText" value="Mark All as Read"/>
|
<userDefinedRuntimeAttribute type="string" keyPath="accLabelText" value="Mark All as Read"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
|
<connections>
|
||||||
|
<action selector="markAllAsRead:" destination="Kyk-vK-QRX" id="EVp-xb-0lW"/>
|
||||||
|
</connections>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
<barButtonItem style="plain" systemItem="flexibleSpace" id="53V-wq-bat"/>
|
<barButtonItem style="plain" systemItem="flexibleSpace" id="53V-wq-bat"/>
|
||||||
<barButtonItem style="plain" systemItem="flexibleSpace" id="93y-8j-WBh"/>
|
<barButtonItem style="plain" systemItem="flexibleSpace" id="93y-8j-WBh"/>
|
||||||
|
@ -589,40 +589,50 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||||||
@objc
|
@objc
|
||||||
func configureContextMenu(_: Any? = nil) {
|
func configureContextMenu(_: Any? = nil) {
|
||||||
if #available(iOS 14.0, *) {
|
if #available(iOS 14.0, *) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Context Menu Order:
|
||||||
|
1. Add Web Feed
|
||||||
|
2. Add Reddit Feed
|
||||||
|
3. Add Twitter Feed
|
||||||
|
4. Add Folder
|
||||||
|
*/
|
||||||
|
|
||||||
|
var menuItems: [UIAction] = []
|
||||||
|
|
||||||
let addWebFeedActionTitle = NSLocalizedString("Add Web Feed", comment: "Add Web Feed")
|
let addWebFeedActionTitle = NSLocalizedString("Add Web Feed", comment: "Add Web Feed")
|
||||||
let addWebFeedAction = UIAction(title: addWebFeedActionTitle, image: AppAssets.faviconTemplateImage.withRenderingMode(.alwaysOriginal).withTintColor(.secondaryLabel)) { _ in
|
let addWebFeedAction = UIAction(title: addWebFeedActionTitle, image: AppAssets.plus) { _ in
|
||||||
self.coordinator.showAddWebFeed()
|
self.coordinator.showAddWebFeed()
|
||||||
}
|
}
|
||||||
|
menuItems.append(addWebFeedAction)
|
||||||
let addRedditFeedActionTitle = NSLocalizedString("Add Reddit Feed", comment: "Add Reddit Feed")
|
|
||||||
let addRedditFeedAction = UIAction(title: addRedditFeedActionTitle, image: AppAssets.redditOriginal) { _ in
|
|
||||||
self.coordinator.showAddRedditFeed()
|
|
||||||
}
|
|
||||||
|
|
||||||
let addTwitterFeedActionTitle = NSLocalizedString("Add Twitter Feed", comment: "Add Twitter Feed")
|
|
||||||
let addTwitterFeedAction = UIAction(title: addTwitterFeedActionTitle, image: AppAssets.twitterOriginal) { _ in
|
|
||||||
self.coordinator.showAddTwitterFeed()
|
|
||||||
}
|
|
||||||
|
|
||||||
let addWebFolderdActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder")
|
|
||||||
let addWebFolderAction = UIAction(title: addWebFolderdActionTitle, image: AppAssets.masterFolderImageNonIcon) { _ in
|
|
||||||
self.coordinator.showAddFolder()
|
|
||||||
}
|
|
||||||
|
|
||||||
var children = [addWebFolderAction, addWebFeedAction]
|
|
||||||
|
|
||||||
|
|
||||||
if AccountManager.shared.activeAccounts.contains(where: { $0.type == .onMyMac || $0.type == .cloudKit }) {
|
if AccountManager.shared.activeAccounts.contains(where: { $0.type == .onMyMac || $0.type == .cloudKit }) {
|
||||||
if ExtensionPointManager.shared.isRedditEnabled {
|
if ExtensionPointManager.shared.isRedditEnabled {
|
||||||
children.insert(addRedditFeedAction, at: 0)
|
let addRedditFeedActionTitle = NSLocalizedString("Add Reddit Feed", comment: "Add Reddit Feed")
|
||||||
|
let addRedditFeedAction = UIAction(title: addRedditFeedActionTitle, image: AppAssets.contextMenuReddit.tinted(color: .label)) { _ in
|
||||||
|
self.coordinator.showAddRedditFeed()
|
||||||
|
}
|
||||||
|
menuItems.append(addRedditFeedAction)
|
||||||
}
|
}
|
||||||
if ExtensionPointManager.shared.isTwitterEnabled {
|
if ExtensionPointManager.shared.isTwitterEnabled {
|
||||||
children.insert(addTwitterFeedAction, at: 0)
|
let addTwitterFeedActionTitle = NSLocalizedString("Add Twitter Feed", comment: "Add Twitter Feed")
|
||||||
|
let addTwitterFeedAction = UIAction(title: addTwitterFeedActionTitle, image: AppAssets.contextMenuTwitter.tinted(color: .label)) { _ in
|
||||||
|
self.coordinator.showAddTwitterFeed()
|
||||||
|
}
|
||||||
|
menuItems.append(addTwitterFeedAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let menu = UIMenu(title: "Add Item", image: nil, identifier: nil, options: [], children: children)
|
|
||||||
|
let addWebFolderActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder")
|
||||||
|
let addWebFolderAction = UIAction(title: addWebFolderActionTitle, image: AppAssets.folderOutlinePlus) { _ in
|
||||||
|
self.coordinator.showAddFolder()
|
||||||
|
}
|
||||||
|
|
||||||
self.addNewItemButton.menu = menu
|
menuItems.append(addWebFolderAction)
|
||||||
|
|
||||||
|
let contextMenu = UIMenu(title: NSLocalizedString("Add Item", comment: "Add Item"), image: nil, identifier: nil, options: [], children: menuItems.reversed())
|
||||||
|
|
||||||
|
self.addNewItemButton.menu = contextMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1225,7 +1235,7 @@ private extension MasterFeedViewController {
|
|||||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, account.nameForDisplay) as String
|
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, account.nameForDisplay) as String
|
||||||
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
|
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||||
if let articles = try? account.fetchArticles(.unread) {
|
if let articles = try? account.fetchArticles(.unread()) {
|
||||||
self?.coordinator.markAllAsRead(Array(articles))
|
self?.coordinator.markAllAsRead(Array(articles))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,15 +19,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||||||
|
|
||||||
private var refreshProgressView: RefreshProgressView?
|
private var refreshProgressView: RefreshProgressView?
|
||||||
|
|
||||||
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem! {
|
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem!
|
||||||
didSet {
|
|
||||||
if #available(iOS 14, *) {
|
|
||||||
markAllAsReadButton.primaryAction = nil
|
|
||||||
} else {
|
|
||||||
markAllAsReadButton.action = #selector(MasterTimelineViewController.markAllAsRead(_:))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var filterButton: UIBarButtonItem!
|
private var filterButton: UIBarButtonItem!
|
||||||
private var firstUnreadButton: UIBarButtonItem!
|
private var firstUnreadButton: UIBarButtonItem!
|
||||||
@ -666,25 +658,6 @@ private extension MasterTimelineViewController {
|
|||||||
setToolbarItems(items, animated: false)
|
setToolbarItems(items, animated: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(iOS 14, *) {
|
|
||||||
let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
|
|
||||||
var markAsReadAction: UIAction!
|
|
||||||
|
|
||||||
if AppDefaults.shared.confirmMarkAllAsRead {
|
|
||||||
markAsReadAction = UIAction(title: title, image: AppAssets.markAllAsReadImage, discoverabilityTitle: "in \(self.title!)") { [weak self] action in
|
|
||||||
self?.coordinator.markAllAsReadInTimeline()
|
|
||||||
}
|
|
||||||
let settingsAction = UIAction(title: NSLocalizedString("Settings", comment: "Settings"), image: UIImage(systemName: "gear")!, discoverabilityTitle: NSLocalizedString("You can turn this confirmation off in Settings.", comment: "You can turn this confirmation off in Settings.")) { [weak self] action in
|
|
||||||
self?.coordinator.showSettings(scrollToArticlesSection: true)
|
|
||||||
}
|
|
||||||
markAllAsReadButton.menu = UIMenu(title: NSLocalizedString(title, comment: title), image: nil, identifier: nil, children: [settingsAction, markAsReadAction])
|
|
||||||
markAllAsReadButton.action = nil
|
|
||||||
} else {
|
|
||||||
markAllAsReadButton.action = #selector(MasterTimelineViewController.markAllAsRead(_:))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTitleUnreadCount() {
|
func updateTitleUnreadCount() {
|
||||||
|
12
iOS/Resources/Assets.xcassets/contextMenuReddit.imageset/Contents.json
vendored
Normal file
12
iOS/Resources/Assets.xcassets/contextMenuReddit.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "redditContextMenu.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
iOS/Resources/Assets.xcassets/contextMenuReddit.imageset/redditContextMenu.pdf
vendored
Normal file
BIN
iOS/Resources/Assets.xcassets/contextMenuReddit.imageset/redditContextMenu.pdf
vendored
Normal file
Binary file not shown.
12
iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/Contents.json
vendored
Normal file
12
iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "twitterContextMenu.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/twitterContextMenu.pdf
vendored
Normal file
BIN
iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/twitterContextMenu.pdf
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user