Merge branch 'ios-candidate'
This commit is contained in:
commit
f9af3c786b
@ -13,7 +13,7 @@ let package = Package(
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "1.0.0")),
|
||||
.package(url: "https://github.com/Ranchero-Software/RSDatabase.git", .upToNextMajor(from: "1.0.0")),
|
||||
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.0")),
|
||||
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2")),
|
||||
.package(url: "https://github.com/Ranchero-Software/RSWeb.git", .upToNextMajor(from: "1.0.0")),
|
||||
.package(url: "../Articles", .upToNextMajor(from: "1.0.0")),
|
||||
.package(url: "../ArticlesDatabase", .upToNextMajor(from: "1.0.0")),
|
||||
|
@ -180,7 +180,11 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
if let ckError = ((error as? CloudKitError)?.error as? CKError), ckError.code == .unknownItem {
|
||||
completion(.success(true))
|
||||
} else {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,9 @@ import Articles
|
||||
class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
|
||||
private typealias UnclaimedWebFeed = (url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String)
|
||||
private var unclaimedWebFeeds = [String: [UnclaimedWebFeed]]()
|
||||
|
||||
private var newUnclaimedWebFeeds = [String: [UnclaimedWebFeed]]()
|
||||
private var existingUnclaimedWebFeeds = [String: [WebFeed]]()
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||
|
||||
weak var account: Account?
|
||||
@ -75,7 +76,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
if let container = account.existingContainer(withExternalID: containerExternalID) {
|
||||
createWebFeedIfNecessary(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, container: container)
|
||||
} else {
|
||||
addUnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, containerExternalID: containerExternalID)
|
||||
addNewUnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, containerExternalID: containerExternalID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,19 +107,27 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
folder?.externalID = record.externalID
|
||||
}
|
||||
|
||||
if let folder = folder, let containerExternalID = folder.externalID, let unclaimedWebFeeds = unclaimedWebFeeds[containerExternalID] {
|
||||
for unclaimedWebFeed in unclaimedWebFeeds {
|
||||
createWebFeedIfNecessary(url: unclaimedWebFeed.url,
|
||||
name: unclaimedWebFeed.name,
|
||||
editedName: unclaimedWebFeed.editedName,
|
||||
homePageURL: unclaimedWebFeed.homePageURL,
|
||||
webFeedExternalID: unclaimedWebFeed.webFeedExternalID,
|
||||
container: folder)
|
||||
guard let container = folder, let containerExternalID = container.externalID else { return }
|
||||
|
||||
if let newUnclaimedWebFeeds = newUnclaimedWebFeeds[containerExternalID] {
|
||||
for newUnclaimedWebFeed in newUnclaimedWebFeeds {
|
||||
createWebFeedIfNecessary(url: newUnclaimedWebFeed.url,
|
||||
name: newUnclaimedWebFeed.name,
|
||||
editedName: newUnclaimedWebFeed.editedName,
|
||||
homePageURL: newUnclaimedWebFeed.homePageURL,
|
||||
webFeedExternalID: newUnclaimedWebFeed.webFeedExternalID,
|
||||
container: container)
|
||||
}
|
||||
|
||||
self.unclaimedWebFeeds.removeValue(forKey: containerExternalID)
|
||||
self.newUnclaimedWebFeeds.removeValue(forKey: containerExternalID)
|
||||
}
|
||||
|
||||
if let existingUnclaimedWebFeeds = existingUnclaimedWebFeeds[containerExternalID] {
|
||||
for existingUnclaimedWebFeed in existingUnclaimedWebFeeds {
|
||||
container.addWebFeed(existingUnclaimedWebFeed)
|
||||
}
|
||||
self.existingUnclaimedWebFeeds.removeValue(forKey: containerExternalID)
|
||||
}
|
||||
}
|
||||
|
||||
func removeContainer(_ externalID: String) {
|
||||
@ -152,6 +161,8 @@ private extension CloudKitAcountZoneDelegate {
|
||||
case .insert(_, let externalID, _):
|
||||
if let container = account.existingContainer(withExternalID: externalID) {
|
||||
container.addWebFeed(webFeed)
|
||||
} else {
|
||||
addExistingUnclaimedWebFeed(webFeed, containerExternalID: externalID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,14 +181,25 @@ private extension CloudKitAcountZoneDelegate {
|
||||
container.addWebFeed(webFeed)
|
||||
}
|
||||
|
||||
func addUnclaimedWebFeed(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, containerExternalID: String) {
|
||||
if var unclaimedWebFeeds = self.unclaimedWebFeeds[containerExternalID] {
|
||||
func addNewUnclaimedWebFeed(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, containerExternalID: String) {
|
||||
if var unclaimedWebFeeds = self.newUnclaimedWebFeeds[containerExternalID] {
|
||||
unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: webFeedExternalID))
|
||||
self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
self.newUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
} else {
|
||||
var unclaimedWebFeeds = [UnclaimedWebFeed]()
|
||||
unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: webFeedExternalID))
|
||||
self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
self.newUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
}
|
||||
}
|
||||
|
||||
func addExistingUnclaimedWebFeed(_ webFeed: WebFeed, containerExternalID: String) {
|
||||
if var unclaimedWebFeeds = self.existingUnclaimedWebFeeds[containerExternalID] {
|
||||
unclaimedWebFeeds.append(webFeed)
|
||||
self.existingUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
} else {
|
||||
var unclaimedWebFeeds = [WebFeed]()
|
||||
unclaimedWebFeeds.append(webFeed)
|
||||
self.existingUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,21 +78,21 @@ private extension TwitterStatus {
|
||||
|
||||
var html = String()
|
||||
var prevIndex = displayStartIndex
|
||||
var emojiOffset = 0
|
||||
var unicodeScalarOffset = 0
|
||||
|
||||
for entity in entities {
|
||||
|
||||
// The twitter indices are messed up by emoji with more than one scalar, we are going to adjust for that here.
|
||||
let emojiEndIndex = text.index(text.startIndex, offsetBy: entity.endIndex, limitedBy: text.endIndex) ?? text.endIndex
|
||||
if prevIndex < emojiEndIndex {
|
||||
let emojis = String(text[prevIndex..<emojiEndIndex]).emojis
|
||||
for emoji in emojis {
|
||||
emojiOffset += emoji.unicodeScalars.count - 1
|
||||
// The twitter indices are messed up by characters with more than one scalar, we are going to adjust for that here.
|
||||
let endIndex = text.index(text.startIndex, offsetBy: entity.endIndex, limitedBy: text.endIndex) ?? text.endIndex
|
||||
if prevIndex < endIndex {
|
||||
let characters = String(text[prevIndex..<endIndex])
|
||||
for character in characters {
|
||||
unicodeScalarOffset += character.unicodeScalars.count - 1
|
||||
}
|
||||
}
|
||||
|
||||
let offsetStartIndex = entity.startIndex - emojiOffset
|
||||
let offsetEndIndex = entity.endIndex - emojiOffset
|
||||
let offsetStartIndex = entity.startIndex - unicodeScalarOffset
|
||||
let offsetEndIndex = entity.endIndex - unicodeScalarOffset
|
||||
|
||||
let entityStartIndex = text.index(text.startIndex, offsetBy: offsetStartIndex, limitedBy: text.endIndex) ?? text.startIndex
|
||||
let entityEndIndex = text.index(text.startIndex, offsetBy: offsetEndIndex, limitedBy: text.endIndex) ?? text.endIndex
|
||||
|
@ -10,7 +10,7 @@ import Foundation
|
||||
import RSParser
|
||||
import RSCore
|
||||
|
||||
final class FeedbinEntry: Codable {
|
||||
final class FeedbinEntry: Decodable {
|
||||
|
||||
let articleID: Int
|
||||
let feedID: Int
|
||||
@ -50,14 +50,25 @@ final class FeedbinEntry: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedbinEntryJSONFeed: Codable {
|
||||
struct FeedbinEntryJSONFeed: Decodable {
|
||||
let jsonFeedAuthor: FeedbinEntryJSONFeedAuthor?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case jsonFeedAuthor = "author"
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
do {
|
||||
jsonFeedAuthor = try container.decode(FeedbinEntryJSONFeedAuthor.self, forKey: .jsonFeedAuthor)
|
||||
} catch {
|
||||
jsonFeedAuthor = nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct FeedbinEntryJSONFeedAuthor: Codable {
|
||||
struct FeedbinEntryJSONFeedAuthor: Decodable {
|
||||
let url: String?
|
||||
let avatarURL: String?
|
||||
enum CodingKeys: String, CodingKey {
|
||||
|
@ -310,7 +310,7 @@ final class FeedlyAPICaller {
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||
|
||||
send(request: request, resultType: String.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
||||
send(request: request, resultType: Optional<FeedlyCollection>.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
||||
switch result {
|
||||
case .success(let (httpResponse, _)):
|
||||
if httpResponse.statusCode == 200 {
|
||||
|
@ -133,7 +133,7 @@ final class NewsBlurAPICaller: NSObject {
|
||||
URLQueryItem(name: "page", value: String(page)),
|
||||
URLQueryItem(name: "order", value: "newest"),
|
||||
URLQueryItem(name: "read_filter", value: "all"),
|
||||
URLQueryItem(name: "include_hidden", value: "true"),
|
||||
URLQueryItem(name: "include_hidden", value: "false"),
|
||||
URLQueryItem(name: "include_story_content", value: "true"),
|
||||
])
|
||||
|
||||
@ -150,7 +150,7 @@ final class NewsBlurAPICaller: NSObject {
|
||||
func retrieveStories(hashes: [NewsBlurStoryHash], completion: @escaping (Result<([NewsBlurStory]?, Date?), Error>) -> Void) {
|
||||
let url = baseURL
|
||||
.appendingPathComponent("reader/river_stories")
|
||||
.appendingQueryItem(.init(name: "include_hidden", value: "true"))?
|
||||
.appendingQueryItem(.init(name: "include_hidden", value: "false"))?
|
||||
.appendingQueryItems(hashes.map {
|
||||
URLQueryItem(name: "h", value: $0.hash)
|
||||
})
|
||||
|
@ -14,10 +14,24 @@ import SyncDatabase
|
||||
import os.log
|
||||
import Secrets
|
||||
|
||||
public enum ReaderAPIAccountDelegateError: String, Error {
|
||||
case unknown = "An unknown error occurred."
|
||||
case invalidParameter = "There was an invalid parameter passed."
|
||||
case invalidResponse = "There was an invalid response from the server."
|
||||
public enum ReaderAPIAccountDelegateError: LocalizedError {
|
||||
case unknown
|
||||
case invalidParameter
|
||||
case invalidResponse
|
||||
case urlNotFound
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .unknown:
|
||||
return NSLocalizedString("An unexpected error occurred.", comment: "An unexpected error occurred.")
|
||||
case .invalidParameter:
|
||||
return NSLocalizedString("An invalid parameter was passed.", comment: "An invalid parameter was passed.")
|
||||
case .invalidResponse:
|
||||
return NSLocalizedString("There was an invalid response from the server.", comment: "There was an invalid response from the server.")
|
||||
case .urlNotFound:
|
||||
return NSLocalizedString("The API URL wasn't found.", comment: "The API URL wasn't found.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
@ -132,7 +132,11 @@ final class ReaderAPICaller: NSObject {
|
||||
|
||||
completion(.success(self.credentials))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
if let transportError = error as? TransportError, case .httpError(let code) = transportError, code == 404 {
|
||||
completion(.failure(ReaderAPIAccountDelegateError.urlNotFound))
|
||||
} else {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ let package = Package(
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "1.0.0")),
|
||||
.package(url: "https://github.com/Ranchero-Software/RSDatabase.git", .upToNextMajor(from: "1.0.0")),
|
||||
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.0")),
|
||||
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2")),
|
||||
.package(url: "../Articles", .upToNextMajor(from: "1.0.0")),
|
||||
],
|
||||
targets: [
|
||||
|
@ -149,18 +149,7 @@ private extension WebFeedInspectorViewController {
|
||||
guard let feed = feed, let iconView = iconView else {
|
||||
return
|
||||
}
|
||||
|
||||
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) {
|
||||
iconView.iconImage = feedIcon
|
||||
return
|
||||
}
|
||||
|
||||
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
|
||||
iconView.iconImage = favicon
|
||||
return
|
||||
}
|
||||
|
||||
iconView.iconImage = feed.smallIcon
|
||||
iconView.iconImage = IconImageCache.shared.imageForFeed(feed)
|
||||
}
|
||||
|
||||
func updateName() {
|
||||
|
@ -768,7 +768,7 @@ private extension SidebarViewController {
|
||||
}
|
||||
|
||||
func imageFor(_ node: Node) -> IconImage? {
|
||||
if let feed = node.representedObject as? WebFeed, let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) {
|
||||
if let feed = node.representedObject as? WebFeed, let feedIcon = IconImageCache.shared.imageForFeed(feed) {
|
||||
return feedIcon
|
||||
}
|
||||
if let smallIconProvider = node.representedObject as? SmallIconProvider {
|
||||
|
@ -38,7 +38,7 @@ extension Article: PasteboardWriterOwner {
|
||||
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
|
||||
var types = [ArticlePasteboardWriter.articleUTIType]
|
||||
|
||||
if let link = article.preferredLink, let _ = URL(string: link) {
|
||||
if let _ = article.preferredURL {
|
||||
types += [.URL]
|
||||
}
|
||||
types += [.string, .html, ArticlePasteboardWriter.articleUTIInternalType]
|
||||
|
@ -886,28 +886,7 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||
if !showIcons {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let authors = article.authors {
|
||||
for author in authors {
|
||||
if let image = avatarForAuthor(author) {
|
||||
return image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let feed = article.webFeed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) {
|
||||
return feedIcon
|
||||
}
|
||||
|
||||
if let favicon = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
return favicon
|
||||
}
|
||||
|
||||
return FaviconGenerator.favicon(feed)
|
||||
return IconImageCache.shared.imageForArticle(article)
|
||||
}
|
||||
|
||||
private func avatarForAuthor(_ author: Author) -> IconImage? {
|
||||
|
@ -107,7 +107,7 @@ struct ArticleToolbarModifier: ViewModifier {
|
||||
.disabled(sceneModel.shareButtonState == nil)
|
||||
.help("Share")
|
||||
.sheet(isPresented: $showActivityView) {
|
||||
if let article = sceneModel.selectedArticles.first, let link = article.preferredLink, let url = URL(string: link) {
|
||||
if let article = sceneModel.selectedArticles.first, let url = article.preferredURL {
|
||||
ActivityViewController(title: article.title, url: url)
|
||||
}
|
||||
}
|
||||
|
@ -41,20 +41,6 @@ final class FeedIconImageLoader: ObservableObject {
|
||||
private extension FeedIconImageLoader {
|
||||
|
||||
func fetchImage() {
|
||||
if let webFeed = feed as? WebFeed {
|
||||
if let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed) {
|
||||
image = feedIconImage
|
||||
return
|
||||
}
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
|
||||
image = faviconImage
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let smallIconProvider = feed as? SmallIconProvider {
|
||||
image = smallIconProvider.smallIcon
|
||||
}
|
||||
image = IconImageCache.shared.imageForFeed(feed)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -249,12 +249,11 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
||||
}
|
||||
|
||||
func openIndicatedArticleInBrowser(_ article: Article) {
|
||||
guard let link = article.preferredLink else { return }
|
||||
|
||||
#if os(macOS)
|
||||
guard let link = article.preferredLink else { return }
|
||||
Browser.open(link, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false)
|
||||
#else
|
||||
guard let url = URL(string: link) else { return }
|
||||
guard let url = article.preferredURL else { return }
|
||||
UIApplication.shared.open(url, options: [:])
|
||||
#endif
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2511
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2513
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;}
|
||||
\margl1440\margr1440\vieww9000\viewh8400\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\li354\fi-355\sa60\partightenfactor0
|
||||
\pard\pardeftab720\sa60\partightenfactor0
|
||||
|
||||
\f0\fs28 \cf2 NetNewsWire 5.0 is dedicated to all the people who showed up to help with code, design, HTML, documentation, icons, testing, and just to help talk things over and think things through. This app\'92s for you!}
|
||||
\f0\fs22 \cf2 NetNewsWire 6 is dedicated to everyone working to save democracy around the world.}
|
@ -24,7 +24,7 @@ struct SettingsAboutView: View {
|
||||
Section(header: Text("THANKS")) {
|
||||
AttributedStringView(string: self.viewModel.thanks, preferredMaxLayoutWidth: geometry.size.width - 20)
|
||||
}
|
||||
Section(header: Text("DEDICATION"), footer: Text("Copyright © 2002-2020 BrentSimmons").font(.footnote)) {
|
||||
Section(header: Text("DEDICATION"), footer: Text("Copyright © 2002-2021 Brent Simmons").font(.footnote)) {
|
||||
AttributedStringView(string: self.viewModel.dedication, preferredMaxLayoutWidth: geometry.size.width - 20)
|
||||
}
|
||||
}.listStyle(InsetGroupedListStyle())
|
||||
|
@ -1015,6 +1015,9 @@
|
||||
844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */; };
|
||||
845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
|
||||
845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; };
|
||||
8454C3F3263F2D8700E3F9C7 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; };
|
||||
8454C3F8263F3AD400E3F9C7 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; };
|
||||
8454C3FD263F3AD600E3F9C7 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; };
|
||||
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; };
|
||||
845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */; };
|
||||
845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */; };
|
||||
@ -1901,6 +1904,7 @@
|
||||
844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = SidebarKeyboardShortcuts.plist; sourceTree = "<group>"; };
|
||||
845213221FCA5B10003B6E93 /* ImageDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
|
||||
845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = TimelineKeyboardShortcuts.plist; sourceTree = "<group>"; };
|
||||
8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImageCache.swift; sourceTree = "<group>"; };
|
||||
845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFaviconDownloader.swift; sourceTree = "<group>"; };
|
||||
845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellLayout.swift; sourceTree = "<group>"; };
|
||||
845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellAppearance.swift; sourceTree = "<group>"; };
|
||||
@ -3436,6 +3440,7 @@
|
||||
842E45CD1ED8C308000A8B52 /* AppNotifications.swift */,
|
||||
51C4CFEF24D37D1F00AF9874 /* Secrets.swift */,
|
||||
511B9805237DCAC90028BCAA /* UserInfoKey.swift */,
|
||||
8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */,
|
||||
51C452AD2265102800C03939 /* Timeline */,
|
||||
84702AB31FA27AE8006B8943 /* Commands */,
|
||||
51934CCC231078DC006127BE /* Activity */,
|
||||
@ -5150,6 +5155,7 @@
|
||||
65ED4007235DEF6C0081F399 /* AddFeedController.swift in Sources */,
|
||||
65ED4008235DEF6C0081F399 /* AccountRefreshTimer.swift in Sources */,
|
||||
65ED4009235DEF6C0081F399 /* SidebarStatusBarView.swift in Sources */,
|
||||
8454C3FD263F3AD600E3F9C7 /* IconImageCache.swift in Sources */,
|
||||
65ED400A235DEF6C0081F399 /* SearchTimelineFeedDelegate.swift in Sources */,
|
||||
65ED400B235DEF6C0081F399 /* TodayFeedDelegate.swift in Sources */,
|
||||
65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */,
|
||||
@ -5375,6 +5381,7 @@
|
||||
516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */,
|
||||
51DC370B2405BC9A0095D371 /* PreloadedWebView.swift in Sources */,
|
||||
D3555BF524664566005E48C3 /* ArticleSearchBar.swift in Sources */,
|
||||
8454C3F3263F2D8700E3F9C7 /* IconImageCache.swift in Sources */,
|
||||
B24E9ADE245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */,
|
||||
C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */,
|
||||
51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */,
|
||||
@ -5545,6 +5552,7 @@
|
||||
849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */,
|
||||
841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */,
|
||||
D5E4CC54202C1361009B4FFC /* AppDelegate+Scriptability.swift in Sources */,
|
||||
8454C3F8263F3AD400E3F9C7 /* IconImageCache.swift in Sources */,
|
||||
518651B223555EB20078E021 /* NNW3Document.swift in Sources */,
|
||||
D5F4EDB5200744A700B9E363 /* ScriptingObject.swift in Sources */,
|
||||
D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */,
|
||||
|
@ -248,12 +248,11 @@ private extension ActivityManager {
|
||||
attributeSet.title = feed.nameForDisplay
|
||||
attributeSet.keywords = makeKeywords(feed.nameForDisplay)
|
||||
attributeSet.relatedUniqueIdentifier = ActivityManager.identifer(for: feed)
|
||||
if let iconImage = appDelegate.webFeedIconDownloader.icon(for: feed) {
|
||||
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
|
||||
} else if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
|
||||
if let iconImage = IconImageCache.shared.imageForFeed(feed) {
|
||||
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
|
||||
}
|
||||
|
||||
|
||||
selectingActivity!.contentAttributeSet = attributeSet
|
||||
selectingActivity!.needsSave = true
|
||||
|
||||
|
@ -157,7 +157,7 @@
|
||||
document.addEventListener("click", (ev) =>
|
||||
{
|
||||
if (!(ev.target && ev.target instanceof HTMLAnchorElement)) return;
|
||||
if (!ev.target.matches(".footnotes .reversefootnote, .footnotes .footnoteBackLink, .footnotes .footnote-return")) return;
|
||||
if (!ev.target.matches(".footnotes .reversefootnote, .footnotes .footnoteBackLink, .footnotes .footnote-return, .footnotes a[href*='#fn'], .footnotes a[href^='#']")) return;
|
||||
const id = idFromHash(ev.target);
|
||||
if (!id) return;
|
||||
const fnref = document.getElementById(id);
|
||||
|
@ -382,7 +382,8 @@ img[src*="share-buttons"] {
|
||||
|
||||
.newsfoot-footnote-popover .reversefootnote,
|
||||
.newsfoot-footnote-popover .footnoteBackLink,
|
||||
.newsfoot-footnote-popover .footnote-return {
|
||||
.newsfoot-footnote-popover .footnote-return,
|
||||
.newsfoot-footnote-popover a[href*='#fn'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ final class MarkStatusCommand: UndoableCommand {
|
||||
// Filter out articles that already have the desired status or can't be marked.
|
||||
let articlesToMark = MarkStatusCommand.filteredArticles(initialArticles, statusKey, flag)
|
||||
if articlesToMark.isEmpty {
|
||||
completion?()
|
||||
return nil
|
||||
}
|
||||
self.articles = Set(articlesToMark)
|
||||
|
@ -56,6 +56,18 @@ extension Article {
|
||||
return nil
|
||||
}
|
||||
|
||||
var preferredURL: URL? {
|
||||
guard let link = preferredLink else { return nil }
|
||||
// If required, we replace any space characters to handle malformed links that are otherwise percent
|
||||
// encoded but contain spaces. For performance reasons, only try this if initial URL init fails.
|
||||
if let url = URL(string: link) {
|
||||
return url
|
||||
} else if let url = URL(string: link.replacingOccurrences(of: " ", with: "%20")) {
|
||||
return url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var body: String? {
|
||||
return contentHTML ?? contentText ?? summary
|
||||
}
|
||||
@ -84,32 +96,7 @@ extension Article {
|
||||
}
|
||||
|
||||
func iconImage() -> IconImage? {
|
||||
if let authors = authors, authors.count == 1, let author = authors.first {
|
||||
if let image = appDelegate.authorAvatarDownloader.image(for: author) {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
if let authors = webFeed?.authors, authors.count == 1, let author = authors.first {
|
||||
if let image = appDelegate.authorAvatarDownloader.image(for: author) {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
guard let webFeed = webFeed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed)
|
||||
if feedIconImage != nil {
|
||||
return feedIconImage
|
||||
}
|
||||
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
|
||||
return faviconImage
|
||||
}
|
||||
|
||||
return FaviconGenerator.favicon(webFeed)
|
||||
return IconImageCache.shared.imageForArticle(self)
|
||||
}
|
||||
|
||||
func iconImageUrl(webFeed: WebFeed) -> URL? {
|
||||
|
129
Shared/IconImageCache.swift
Normal file
129
Shared/IconImageCache.swift
Normal file
@ -0,0 +1,129 @@
|
||||
//
|
||||
// IconImageCache.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Brent Simmons on 5/2/21.
|
||||
// Copyright © 2021 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import Articles
|
||||
|
||||
class IconImageCache {
|
||||
|
||||
static var shared = IconImageCache()
|
||||
|
||||
private var smartFeedIconImageCache = [FeedIdentifier: IconImage]()
|
||||
private var webFeedIconImageCache = [FeedIdentifier: IconImage]()
|
||||
private var faviconImageCache = [FeedIdentifier: IconImage]()
|
||||
private var smallIconImageCache = [FeedIdentifier: IconImage]()
|
||||
private var authorIconImageCache = [Author: IconImage]()
|
||||
|
||||
func imageFor(_ feedID: FeedIdentifier) -> IconImage? {
|
||||
if let smartFeed = SmartFeedsController.shared.find(by: feedID) {
|
||||
return imageForFeed(smartFeed)
|
||||
}
|
||||
if let feed = AccountManager.shared.existingFeed(with: feedID) {
|
||||
return imageForFeed(feed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageForFeed(_ feed: Feed) -> IconImage? {
|
||||
guard let feedID = feed.feedID else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let smartFeed = feed as? PseudoFeed {
|
||||
return imageForSmartFeed(smartFeed, feedID)
|
||||
}
|
||||
if let webFeed = feed as? WebFeed, let iconImage = imageForWebFeed(webFeed, feedID) {
|
||||
return iconImage
|
||||
}
|
||||
if let smallIconProvider = feed as? SmallIconProvider {
|
||||
return imageForSmallIconProvider(smallIconProvider, feedID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageForArticle(_ article: Article) -> IconImage? {
|
||||
if let iconImage = imageForAuthors(article.authors) {
|
||||
return iconImage
|
||||
}
|
||||
guard let feed = article.webFeed else {
|
||||
return nil
|
||||
}
|
||||
return imageForFeed(feed)
|
||||
}
|
||||
|
||||
func emptyCache() {
|
||||
smartFeedIconImageCache = [FeedIdentifier: IconImage]()
|
||||
webFeedIconImageCache = [FeedIdentifier: IconImage]()
|
||||
faviconImageCache = [FeedIdentifier: IconImage]()
|
||||
smallIconImageCache = [FeedIdentifier: IconImage]()
|
||||
authorIconImageCache = [Author: IconImage]()
|
||||
}
|
||||
}
|
||||
|
||||
private extension IconImageCache {
|
||||
|
||||
func imageForSmartFeed(_ smartFeed: PseudoFeed, _ feedID: FeedIdentifier) -> IconImage? {
|
||||
if let iconImage = smartFeedIconImageCache[feedID] {
|
||||
return iconImage
|
||||
}
|
||||
if let iconImage = smartFeed.smallIcon {
|
||||
smartFeedIconImageCache[feedID] = iconImage
|
||||
return iconImage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageForWebFeed(_ webFeed: WebFeed, _ feedID: FeedIdentifier) -> IconImage? {
|
||||
if let iconImage = webFeedIconImageCache[feedID] {
|
||||
return iconImage
|
||||
}
|
||||
if let iconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed) {
|
||||
webFeedIconImageCache[feedID] = iconImage
|
||||
return iconImage
|
||||
}
|
||||
if let faviconImage = faviconImageCache[feedID] {
|
||||
return faviconImage
|
||||
}
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
|
||||
faviconImageCache[feedID] = faviconImage
|
||||
return faviconImage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageForSmallIconProvider(_ provider: SmallIconProvider, _ feedID: FeedIdentifier) -> IconImage? {
|
||||
if let iconImage = smallIconImageCache[feedID] {
|
||||
return iconImage
|
||||
}
|
||||
if let iconImage = provider.smallIcon {
|
||||
smallIconImageCache[feedID] = iconImage
|
||||
return iconImage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageForAuthors(_ authors: Set<Author>?) -> IconImage? {
|
||||
guard let authors = authors, authors.count == 1, let author = authors.first else {
|
||||
return nil
|
||||
}
|
||||
return imageForAuthor(author)
|
||||
}
|
||||
|
||||
func imageForAuthor(_ author: Author) -> IconImage? {
|
||||
if let iconImage = authorIconImageCache[author] {
|
||||
return iconImage
|
||||
}
|
||||
if let iconImage = appDelegate.authorAvatarDownloader.image(for: author) {
|
||||
authorIconImageCache[author] = iconImage
|
||||
return iconImage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -56,9 +56,7 @@ private extension WebFeedTreeControllerDelegate {
|
||||
|
||||
func childNodesForSmartFeeds(_ parentNode: Node) -> [Node] {
|
||||
return SmartFeedsController.shared.smartFeeds.compactMap { (feed) -> Node? in
|
||||
if let feedID = feed.feedID, !filterExceptions.contains(feedID) && isReadFiltered && feed.unreadCount == 0 {
|
||||
return nil
|
||||
}
|
||||
// All Smart Feeds should remain visible despite the Hide Read Feeds setting
|
||||
return parentNode.existingOrNewChildNode(with: feed as AnyObject)
|
||||
}
|
||||
}
|
||||
|
25
Technotes/ReleaseNotes-iOS.markdown
Normal file
25
Technotes/ReleaseNotes-iOS.markdown
Normal file
@ -0,0 +1,25 @@
|
||||
# iOS Release Notes
|
||||
|
||||
### 6.0 TestFlight build 603 - 16 May 2021
|
||||
|
||||
Feedly: handle Feedly API change with return value on deleting a folder
|
||||
NewsBlur: sync no longer includes items marked as hidden on NewsBlur
|
||||
FreshRSS: form for adding account now suggests endpoing URL
|
||||
FreshRSS: improved the error message for when the API URL can’t be found
|
||||
iCloud: retain existing feeds moved to a folder that doesn’t exist yet (sync ordering issue)
|
||||
Renamed a Delete Account button to Remove Account
|
||||
iCloud: skip displaying an error message on deleting a feed that doesn’t exist in iCloud
|
||||
Preferences: Tweaked text explaining Feed Providers
|
||||
Feeds list: context menu for smart feeds is back (regression fix)
|
||||
Feeds list: all smart feeds remain visible despite Hide Read Feeds setting
|
||||
Article view: fixed zoom issue on iPad on rotation
|
||||
Article view: fixed bug where mark-read button on toolbar would flash on navigating to an unread article
|
||||
Article view: made footnote detection more robust
|
||||
Fixed regression on iPad where timeline and article wouldn’t update after the selected feed was deleted
|
||||
Sharing: handle feeds where the URL has unencoded space characters (why a feed would do that is beyond our ken)
|
||||
|
||||
### 6.0 TestFlight build 602 - 21 April 2021
|
||||
|
||||
Inoreader: don’t call it so often, so we don’t go over the API limits
|
||||
Feedly: handle a specific case where Feedly started not returning a value we expected but didn’t actually need (we were reporting it as an error to the user, but it wasn’t)
|
||||
|
@ -50,6 +50,7 @@ class ReaderAPIAccountViewController: UITableViewController {
|
||||
switch unwrappedAccountType {
|
||||
case .freshRSS:
|
||||
title = NSLocalizedString("FreshRSS", comment: "FreshRSS")
|
||||
apiURLTextField.placeholder = NSLocalizedString("API URL: fresh.rss.net/api/greader.php", comment: "FreshRSS API Helper")
|
||||
case .inoreader:
|
||||
title = NSLocalizedString("InoReader", comment: "InoReader")
|
||||
case .bazQux:
|
||||
|
@ -132,6 +132,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
shuttingDown = true
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
IconImageCache.shared.emptyCache()
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
|
@ -79,6 +79,13 @@ class WebViewController: UIViewController {
|
||||
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
// We need to reload the webview on the iPhone when rotation happens to clear out any old bad viewport sizes
|
||||
if traitCollection.userInterfaceIdiom == .phone {
|
||||
loadWebView()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
@ -235,20 +242,14 @@ class WebViewController: UIViewController {
|
||||
}
|
||||
|
||||
func showActivityDialog(popOverBarButtonItem: UIBarButtonItem? = nil) {
|
||||
guard let preferredLink = article?.preferredLink, let url = URL(string: preferredLink) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = article?.preferredURL else { return }
|
||||
let activityViewController = UIActivityViewController(url: url, title: article?.title, applicationActivities: [FindInArticleActivity(), OpenInBrowserActivity()])
|
||||
activityViewController.popoverPresentationController?.barButtonItem = popOverBarButtonItem
|
||||
present(activityViewController, animated: true)
|
||||
}
|
||||
|
||||
func openInAppBrowser() {
|
||||
guard let preferredLink = article?.preferredLink, let url = URL(string: preferredLink) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = article?.preferredURL else { return }
|
||||
let vc = SFSafariViewController(url: url)
|
||||
present(vc, animated: true)
|
||||
}
|
||||
|
@ -12,33 +12,24 @@ final class IconView: UIView {
|
||||
|
||||
var iconImage: IconImage? = nil {
|
||||
didSet {
|
||||
if iconImage !== oldValue {
|
||||
imageView.image = iconImage?.image
|
||||
|
||||
if self.traitCollection.userInterfaceStyle == .dark {
|
||||
if self.iconImage?.isDark ?? false {
|
||||
self.isDiscernable = false
|
||||
self.setNeedsLayout()
|
||||
} else {
|
||||
self.isDiscernable = true
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
} else {
|
||||
if self.iconImage?.isBright ?? false {
|
||||
self.isDiscernable = false
|
||||
self.setNeedsLayout()
|
||||
} else {
|
||||
self.isDiscernable = true
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
self.setNeedsLayout()
|
||||
guard iconImage !== oldValue else {
|
||||
return
|
||||
}
|
||||
imageView.image = iconImage?.image
|
||||
if traitCollection.userInterfaceStyle == .dark {
|
||||
let isDark = iconImage?.isDark ?? false
|
||||
isDiscernable = !isDark
|
||||
}
|
||||
else {
|
||||
let isBright = iconImage?.isBright ?? false
|
||||
isDiscernable = !isBright
|
||||
}
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private var isDiscernable = true
|
||||
|
||||
|
||||
private let imageView: UIImageView = {
|
||||
let imageView = NonIntrinsicImageView(image: AppAssets.faviconTemplateImage)
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
@ -79,13 +70,8 @@ final class IconView: UIView {
|
||||
|
||||
override func layoutSubviews() {
|
||||
imageView.setFrameIfNotEqual(rectForImageView())
|
||||
if !isBackgroundSuppressed && ((iconImage != nil && isVerticalBackgroundExposed) || !isDiscernable) {
|
||||
backgroundColor = AppAssets.iconBackgroundColor
|
||||
} else {
|
||||
backgroundColor = nil
|
||||
}
|
||||
updateBackgroundColor()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension IconView {
|
||||
@ -125,4 +111,11 @@ private extension IconView {
|
||||
return CGRect(x: 0.0, y: originY, width: viewSize.width, height: height)
|
||||
}
|
||||
|
||||
private func updateBackgroundColor() {
|
||||
if !isBackgroundSuppressed && ((iconImage != nil && isVerticalBackgroundExposed) || !isDiscernable) {
|
||||
backgroundColor = AppAssets.iconBackgroundColor
|
||||
} else {
|
||||
backgroundColor = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,13 +92,13 @@ class AccountInspectorViewController: UITableViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let title = NSLocalizedString("Delete Account", comment: "Delete Account")
|
||||
let title = NSLocalizedString("Remove Account", comment: "Remove Account")
|
||||
let message: String = {
|
||||
switch account.type {
|
||||
case .feedly:
|
||||
return NSLocalizedString("Are you sure you want to delete this account? NetNewsWire will no longer be able to access articles and feeds unless the account is added again.", comment: "Log Out and Delete Account")
|
||||
return NSLocalizedString("Are you sure you want to remove this account? NetNewsWire will no longer be able to access articles and feeds unless the account is added again.", comment: "Log Out and Remove Account")
|
||||
default:
|
||||
return NSLocalizedString("Are you sure you want to delete this account? This cannot be undone.", comment: "Delete Account")
|
||||
return NSLocalizedString("Are you sure you want to remove this account? This cannot be undone.", comment: "Remove Account")
|
||||
}
|
||||
}()
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
@ -106,7 +106,7 @@ class AccountInspectorViewController: UITableViewController {
|
||||
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel)
|
||||
alertController.addAction(cancelAction)
|
||||
|
||||
let markTitle = NSLocalizedString("Delete", comment: "Delete")
|
||||
let markTitle = NSLocalizedString("Remove", comment: "Remove")
|
||||
let markAction = UIAlertAction(title: markTitle, style: .default) { [weak self] (action) in
|
||||
guard let self = self, let account = self.account else { return }
|
||||
AccountManager.shared.deleteAccount(account)
|
||||
|
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@ -27,10 +27,10 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="19" translatesAutoresizingMaskIntoConstraints="NO" id="mQa-0W-eVS">
|
||||
<rect key="frame" x="20" y="11" width="334" height="22"/>
|
||||
<rect key="frame" x="20" y="12.5" width="334" height="18.5"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name (Optional)" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="LUW-uv-piz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="334" height="22"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="334" height="18.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words"/>
|
||||
</textField>
|
||||
@ -52,7 +52,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Active" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zf0-Gm-p4F">
|
||||
<rect key="frame" x="20" y="11.5" width="48" height="21"/>
|
||||
<rect key="frame" x="20" y="13.5" width="40" height="17"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@ -119,7 +119,7 @@
|
||||
<constraint firstAttribute="height" constant="44" id="WtN-fp-Ldt"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<state key="normal" title="Delete Account">
|
||||
<state key="normal" title="Remove Account">
|
||||
<color key="titleColor" systemColor="systemRedColor"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
@ -179,7 +179,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="ZdA-rl-9eP">
|
||||
<rect key="frame" x="20" y="11" width="334" height="22"/>
|
||||
<rect key="frame" x="20" y="13" width="334" height="18.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words"/>
|
||||
</textField>
|
||||
@ -199,7 +199,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Notify About New Articles" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YV2-gG-lMP">
|
||||
<rect key="frame" x="24" y="11.5" width="197" height="21"/>
|
||||
<rect key="frame" x="24" y="13.5" width="167.5" height="17"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@ -229,7 +229,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Always Use Reader View" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bf4-3X-Rfr">
|
||||
<rect key="frame" x="24" y="11.5" width="187" height="21"/>
|
||||
<rect key="frame" x="24" y="13.5" width="158.5" height="17"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@ -256,7 +256,7 @@
|
||||
<tableViewSection headerTitle="Home Page" id="dTd-6q-SZd">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="0zc-o6-Sjh" customClass="VibrantBasicTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="204.5" width="374" height="43.5"/>
|
||||
<rect key="frame" x="20" y="198.5" width="374" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0zc-o6-Sjh" id="vJs-XK-ebf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
|
||||
@ -300,7 +300,7 @@
|
||||
<tableViewSection headerTitle="Feed URL" id="MtQ-oG-lrU">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="fKD-Vi-B8O">
|
||||
<rect key="frame" x="20" y="304" width="374" height="43.5"/>
|
||||
<rect key="frame" x="20" y="292" width="374" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fKD-Vi-B8O" id="2G0-9f-qwN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
|
||||
|
@ -23,14 +23,8 @@ class WebFeedInspectorViewController: UITableViewController {
|
||||
@IBOutlet weak var feedURLLabel: InteractiveLabel!
|
||||
|
||||
private var headerView: InspectorIconHeaderView?
|
||||
private var iconImage: IconImage {
|
||||
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: webFeed) {
|
||||
return feedIcon
|
||||
}
|
||||
if let favicon = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
|
||||
return favicon
|
||||
}
|
||||
return FaviconGenerator.favicon(webFeed)
|
||||
private var iconImage: IconImage? {
|
||||
return IconImageCache.shared.imageForFeed(webFeed)
|
||||
}
|
||||
|
||||
private let homePageIndexPath = IndexPath(row: 0, section: 1)
|
||||
|
@ -41,7 +41,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
@ -85,6 +85,12 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
IconImageCache.shared.emptyCache()
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
reloadAllVisibleCells()
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
@ -842,38 +848,12 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func configureIcon(_ cell: MasterFeedTableViewCell, _ identifier: MasterFeedTableViewIdentifier) {
|
||||
cell.iconImage = imageFor(identifier)
|
||||
guard let feedID = identifier.feedID else {
|
||||
return
|
||||
}
|
||||
cell.iconImage = IconImageCache.shared.imageFor(feedID)
|
||||
}
|
||||
|
||||
func imageFor(_ identifier: MasterFeedTableViewIdentifier) -> IconImage? {
|
||||
guard let feedID = identifier.feedID else { return nil }
|
||||
|
||||
if let smartFeed = SmartFeedsController.shared.find(by: feedID) {
|
||||
return smartFeed.smallIcon
|
||||
}
|
||||
|
||||
guard let feed = AccountManager.shared.existingFeed(with: feedID) else { return nil }
|
||||
|
||||
if let webFeed = feed as? WebFeed {
|
||||
|
||||
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed)
|
||||
if feedIconImage != nil {
|
||||
return feedIconImage
|
||||
}
|
||||
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
|
||||
return faviconImage
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let smallIconProvider = feed as? SmallIconProvider {
|
||||
return smallIconProvider.smallIcon
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func nameFor(_ node: Node) -> String {
|
||||
if let displayNameProvider = node.representedObject as? DisplayNameProvider {
|
||||
return displayNameProvider.nameForDisplay
|
||||
@ -1211,9 +1191,20 @@ private extension MasterFeedViewController {
|
||||
guard let identifier = dataSource.itemIdentifier(for: indexPath), identifier.unreadCount > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
var smartFeed: Feed?
|
||||
if identifier.isPsuedoFeed {
|
||||
if SmartFeedsController.shared.todayFeed.feedID == identifier.feedID {
|
||||
smartFeed = SmartFeedsController.shared.todayFeed
|
||||
} else if SmartFeedsController.shared.unreadFeed.feedID == identifier.feedID {
|
||||
smartFeed = SmartFeedsController.shared.unreadFeed
|
||||
} else if SmartFeedsController.shared.starredFeed.feedID == identifier.feedID {
|
||||
smartFeed = SmartFeedsController.shared.starredFeed
|
||||
}
|
||||
}
|
||||
|
||||
guard let feedID = identifier.feedID,
|
||||
let feed = AccountManager.shared.existingFeed(with: feedID),
|
||||
let feed = smartFeed ?? AccountManager.shared.existingFeed(with: feedID),
|
||||
feed.unreadCount > 0,
|
||||
let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
return nil
|
||||
@ -1349,13 +1340,12 @@ private extension MasterFeedViewController {
|
||||
ActivityManager.cleanUp(feed)
|
||||
}
|
||||
|
||||
pushUndoableCommand(deleteCommand)
|
||||
deleteCommand.perform()
|
||||
|
||||
if indexPath == coordinator.currentFeedIndexPath {
|
||||
coordinator.selectFeed(indexPath: nil)
|
||||
}
|
||||
|
||||
pushUndoableCommand(deleteCommand)
|
||||
deleteCommand.perform()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -243,8 +243,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
|
||||
// Set up the read action
|
||||
let readTitle = article.status.read ?
|
||||
NSLocalizedString("Unread", comment: "Unread") :
|
||||
NSLocalizedString("Read", comment: "Read")
|
||||
NSLocalizedString("Mark as Unread", comment: "Mark as Unread") :
|
||||
NSLocalizedString("Mark as Read", comment: "Mark as Read")
|
||||
|
||||
let readAction = UIContextualAction(style: .normal, title: readTitle) { [weak self] (action, view, completion) in
|
||||
self?.coordinator.toggleRead(article)
|
||||
@ -889,9 +889,7 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
func openInBrowserAction(_ article: Article) -> UIAction? {
|
||||
guard let preferredLink = article.preferredLink, let _ = URL(string: preferredLink) else {
|
||||
return nil
|
||||
}
|
||||
guard let _ = article.preferredURL else { return nil }
|
||||
let title = NSLocalizedString("Open in Browser", comment: "Open in Browser")
|
||||
let action = UIAction(title: title, image: AppAssets.safariImage) { [weak self] action in
|
||||
self?.coordinator.showBrowserForArticle(article)
|
||||
@ -900,9 +898,7 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
func openInBrowserAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let preferredLink = article.preferredLink, let _ = URL(string: preferredLink) else {
|
||||
return nil
|
||||
}
|
||||
guard let _ = article.preferredURL else { return nil }
|
||||
let title = NSLocalizedString("Open in Browser", comment: "Open in Browser")
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
self?.coordinator.showBrowserForArticle(article)
|
||||
@ -923,10 +919,7 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
func shareAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
|
||||
guard let preferredLink = article.preferredLink, let url = URL(string: preferredLink) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let url = article.preferredURL else { return nil }
|
||||
let title = NSLocalizedString("Share", comment: "Share")
|
||||
let action = UIAction(title: title, image: AppAssets.shareImage) { [weak self] action in
|
||||
self?.shareDialogForTableCell(indexPath: indexPath, url: url, title: article.title)
|
||||
@ -935,10 +928,7 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
func shareAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let preferredLink = article.preferredLink, let url = URL(string: preferredLink) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let url = article.preferredURL else { return nil }
|
||||
let title = NSLocalizedString("Share", comment: "Share")
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
completion(true)
|
||||
|
@ -4,17 +4,14 @@
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;}
|
||||
\margl1440\margr1440\vieww14220\viewh13280\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\li360\fi-360\sa60\partightenfactor0
|
||||
\pard\pardeftab720\sa60\partightenfactor0
|
||||
|
||||
\f0\fs22 \cf2 iOS app design: {\field{\*\fldinst{HYPERLINK "https://inessential.com/"}}{\fldrslt Brent Simmons}} and {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}\
|
||||
Lead iOS developer: {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}\
|
||||
\f0\fs22 \cf2 Lead developer: {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}\
|
||||
App icon: {\field{\*\fldinst{HYPERLINK "https://twitter.com/BradEllis"}}{\fldrslt Brad Ellis}}\
|
||||
\pard\pardeftab720\li366\fi-367\sa60\partightenfactor0
|
||||
\cf2 Feedly syncing: {\field{\*\fldinst{HYPERLINK "https://twitter.com/kielgillard"}}{\fldrslt Kiel Gillard}}\
|
||||
Feedly syncing: {\field{\*\fldinst{HYPERLINK "https://twitter.com/kielgillard"}}{\fldrslt Kiel Gillard}}\
|
||||
NewsBlur syncing: {\field{\*\fldinst{HYPERLINK "https://twitter.com/quanganhdo"}}{\fldrslt Anh Do}}\
|
||||
Under-the-hood magic and CSS stylin\'92s: {\field{\*\fldinst{HYPERLINK "https://github.com/wevah"}}{\fldrslt Nate Weaver}}\
|
||||
\pard\pardeftab720\li362\fi-363\sa60\partightenfactor0
|
||||
\cf2 Newsfoot (JS footnote displayer): {\field{\*\fldinst{HYPERLINK "https://github.com/brehaut/"}}{\fldrslt Andrew Brehaut}}\
|
||||
\pard\pardeftab720\li355\fi-356\sa60\partightenfactor0
|
||||
\cf2 Help book: {\field{\*\fldinst{HYPERLINK "https://nostodnayr.net/"}}{\fldrslt Ryan Dotson}}\
|
||||
\pard\pardeftab720\li358\fi-359\sa60\partightenfactor0
|
||||
\cf2 And featuring contributions from {\field{\*\fldinst{HYPERLINK "https://blog.rizwan.dev/"}}{\fldrslt Rizwan Mohamed Ibrahim}}, {\field{\*\fldinst{HYPERLINK "https://stuartbreckenridge.com/"}}{\fldrslt Stuart Breckenridge}}, {\field{\*\fldinst{HYPERLINK "https://twitter.com/philviso"}}{\fldrslt Phil Viso}}, and {\field{\*\fldinst{HYPERLINK "https://github.com/Ranchero-Software/NetNewsWire/graphs/contributors"}}{\fldrslt many more}}!}
|
||||
Newsfoot (JS footnote displayer): {\field{\*\fldinst{HYPERLINK "https://github.com/brehaut/"}}{\fldrslt Andrew Brehaut}}\
|
||||
Help book: {\field{\*\fldinst{HYPERLINK "https://nostodnayr.net/"}}{\fldrslt Ryan Dotson}}\
|
||||
And featuring contributions from {\field{\*\fldinst{HYPERLINK "https://github.com/danielpunkass"}}{\fldrslt Daniel Jalkut}}, {\field{\*\fldinst{HYPERLINK "https://rhonabwy.com/"}}{\fldrslt Joe Heck}}, {\field{\*\fldinst{HYPERLINK "https://github.com/olofhellman"}}{\fldrslt Olof Hellman}}, {\field{\*\fldinst{HYPERLINK "https://blog.rizwan.dev/"}}{\fldrslt Rizwan Mohamed Ibrahim}}, {\field{\*\fldinst{HYPERLINK "https://stuartbreckenridge.com/"}}{\fldrslt Stuart Breckenridge}}, {\field{\*\fldinst{HYPERLINK "https://twitter.com/philviso"}}{\fldrslt Phil Viso}}, and {\field{\*\fldinst{HYPERLINK "https://github.com/Ranchero-Software/NetNewsWire/graphs/contributors"}}{\fldrslt many more}}!\
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2511
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2513
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;}
|
||||
\margl1440\margr1440\vieww9000\viewh8400\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\li354\fi-355\sa60\partightenfactor0
|
||||
\pard\pardeftab720\sa60\partightenfactor0
|
||||
|
||||
\f0\fs28 \cf2 NetNewsWire 5.0 is dedicated to all the people who showed up to help with code, design, HTML, documentation, icons, testing, and just to help talk things over and think things through. This app\'92s for you!}
|
||||
\f0\fs22 \cf2 NetNewsWire 6 is dedicated to everyone working to save democracy around the world.}
|
@ -146,22 +146,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
// At some point we should refactor the current Feed IndexPath out and only use the timeline feed
|
||||
private(set) var currentFeedIndexPath: IndexPath?
|
||||
|
||||
|
||||
var timelineIconImage: IconImage? {
|
||||
if let feed = timelineFeed as? WebFeed {
|
||||
|
||||
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: feed)
|
||||
if feedIconImage != nil {
|
||||
return feedIconImage
|
||||
}
|
||||
|
||||
if let faviconIconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
return faviconIconImage
|
||||
}
|
||||
|
||||
guard let timelineFeed = timelineFeed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (timelineFeed as? SmallIconProvider)?.smallIcon
|
||||
return IconImageCache.shared.imageForFeed(timelineFeed)
|
||||
}
|
||||
|
||||
private var exceptionArticleFetcher: ArticleFetcher?
|
||||
@ -853,11 +843,11 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
currentArticleViewController = articleViewController!
|
||||
}
|
||||
|
||||
// Mark article as read before navigating to it, so the read status does not flash unread/read on display
|
||||
markArticles(Set([article!]), statusKey: .read, flag: true)
|
||||
|
||||
masterTimelineViewController?.updateArticleSelection(animations: animations)
|
||||
currentArticleViewController.article = article
|
||||
|
||||
markArticles(Set([article!]), statusKey: .read, flag: true)
|
||||
|
||||
}
|
||||
|
||||
func beginSearching() {
|
||||
@ -1238,16 +1228,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
func showBrowserForArticle(_ article: Article) {
|
||||
guard let preferredLink = article.preferredLink, let url = URL(string: preferredLink) else {
|
||||
return
|
||||
}
|
||||
guard let url = article.preferredURL else { return }
|
||||
UIApplication.shared.open(url, options: [:])
|
||||
}
|
||||
|
||||
func showBrowserForCurrentArticle() {
|
||||
guard let preferredLink = currentArticle?.preferredLink, let url = URL(string: preferredLink) else {
|
||||
return
|
||||
}
|
||||
guard let url = currentArticle?.preferredURL else { return }
|
||||
UIApplication.shared.open(url, options: [:])
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class AboutViewController: UITableViewController {
|
||||
let buildLabel = NonIntrinsicLabel(frame: CGRect(x: 32.0, y: 0.0, width: 0.0, height: 0.0))
|
||||
buildLabel.font = UIFont.systemFont(ofSize: 11.0)
|
||||
buildLabel.textColor = UIColor.gray
|
||||
buildLabel.text = NSLocalizedString("Copyright © 2002-2020 Brent Simmons", comment: "Copyright")
|
||||
buildLabel.text = NSLocalizedString("Copyright © 2002-2021 Brent Simmons", comment: "Copyright")
|
||||
buildLabel.numberOfLines = 0
|
||||
buildLabel.sizeToFit()
|
||||
buildLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -44,7 +44,7 @@ class AddExtensionPointViewController: UITableViewController, AddExtensionPointD
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
return NSLocalizedString("Feed Providers allow you to subscribe to web site URL's as if they were RSS feeds.", comment: "Feed Provider Footer")
|
||||
return NSLocalizedString("Feed Providers allow you to subscribe to some pages as if they were RSS feeds.", comment: "Feed Provider Footer")
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
// High Level Settings common to both the iOS application and any extensions we bundle with it
|
||||
MARKETING_VERSION = 6.0
|
||||
CURRENT_PROJECT_VERSION = 601
|
||||
CURRENT_PROJECT_VERSION = 603
|
||||
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||
|
Loading…
x
Reference in New Issue
Block a user