diff --git a/Account/Sources/Account/AccountManager.swift b/Account/Sources/Account/AccountManager.swift index fa5eb2b3b..0989691cc 100644 --- a/Account/Sources/Account/AccountManager.swift +++ b/Account/Sources/Account/AccountManager.swift @@ -11,6 +11,7 @@ import RSCore import RSWeb import Articles import ArticlesDatabase +import RSDatabase // Main thread only. @@ -325,6 +326,26 @@ public final class AccountManager: UnreadCountProvider { return false } + public func anyLocalOriCloudAccountHasAtLeastOneTwitterFeed() -> Bool { + // We removed our Twitter code, and the ability to read feeds from Twitter, + // when Twitter announced the end of the free tier for the Twitter API. + // We are cheering on Twitter’s increasing irrelevancy. + + for account in accounts { + if account.type == .cloudKit || account.type == .onMyMac { + for webfeed in account.flattenedWebFeeds() { + if let components = URLComponents(string: webfeed.url), let host = components.host { + if host == "twitter.com" { // Allow, for instance, blog.twitter.com, which might have an actual RSS feed + return true + } + } + } + } + } + + return false + } + // MARK: - Fetching Articles // These fetch articles from active accounts and return a merged Set
. @@ -340,34 +361,44 @@ public final class AccountManager: UnreadCountProvider { } public func fetchArticlesAsync(_ fetchType: FetchType, _ completion: @escaping ArticleSetResultBlock) { - precondition(Thread.isMainThread) - - var allFetchedArticles = Set
() - let numberOfAccounts = activeAccounts.count - var accountsReporting = 0 - - guard numberOfAccounts > 0 else { - completion(.success(allFetchedArticles)) - return - } - - for account in activeAccounts { - account.fetchArticlesAsync(fetchType) { (articleSetResult) in - accountsReporting += 1 - - switch articleSetResult { - case .success(let articles): - allFetchedArticles.formUnion(articles) - if accountsReporting == numberOfAccounts { - completion(.success(allFetchedArticles)) - } - case .failure(let databaseError): - completion(.failure(databaseError)) - return - } - } - } - } + precondition(Thread.isMainThread) + + guard activeAccounts.count > 0 else { + completion(.success(Set
())) + return + } + + var allFetchedArticles = Set
() + var databaseError: DatabaseError? + let dispatchGroup = DispatchGroup() + + for account in activeAccounts { + + dispatchGroup.enter() + + account.fetchArticlesAsync(fetchType) { (articleSetResult) in + precondition(Thread.isMainThread) + + switch articleSetResult { + case .success(let articles): + allFetchedArticles.formUnion(articles) + case .failure(let error): + databaseError = error + } + + dispatchGroup.leave() + } + } + + dispatchGroup.notify(queue: .main) { + if let databaseError { + completion(.failure(databaseError)) + } + else { + completion(.success(allFetchedArticles)) + } + } + } public func fetchUnreadArticlesBetween(limit: Int? = nil, before: Date? = nil, after: Date? = nil) throws -> Set
{ precondition(Thread.isMainThread) diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterEntities.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterEntities.swift deleted file mode 100644 index 15d4c39ee..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterEntities.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// TwitterEntities.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -protocol TwitterEntity { - var indices: [Int]? { get } - func renderAsHTML() -> String -} - -extension TwitterEntity { - - var startIndex: Int { - if let indices = indices, indices.count > 0 { - return indices[0] - } - return 0 - } - - var endIndex: Int { - if let indices = indices, indices.count > 1 { - return indices[1] - } - return 0 - } - -} - -struct TwitterEntities: Codable { - - let hashtags: [TwitterHashtag]? - let urls: [TwitterURL]? - let userMentions: [TwitterMention]? - let symbols: [TwitterSymbol]? - let media: [TwitterMedia]? - - enum CodingKeys: String, CodingKey { - case hashtags = "hashtags" - case urls = "urls" - case userMentions = "user_mentions" - case symbols = "symbols" - case media = "media" - } - - func combineAndSort() -> [TwitterEntity] { - var entities = [TwitterEntity]() - if let hashtags = hashtags { - entities.append(contentsOf: hashtags) - } - if let urls = urls { - entities.append(contentsOf: urls) - } - if let userMentions = userMentions { - entities.append(contentsOf: userMentions) - } - if let symbols = symbols { - entities.append(contentsOf: symbols) - } - if let media = media { - entities.append(contentsOf: media) - } - return entities.sorted(by: { $0.startIndex < $1.startIndex }) - } - -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift deleted file mode 100644 index e449e66ca..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// TwitterExtendedEntities.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterExtendedEntities: Codable { - - let medias: [TwitterExtendedMedia]? - - enum CodingKeys: String, CodingKey { - case medias = "media" - } - - func renderAsHTML() -> String { - var html = String() - if let medias = medias { - for media in medias { - html += media.renderAsHTML() - } - } - return html - } -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift deleted file mode 100644 index 614fd5438..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// TwitterExtendedMedia.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterExtendedMedia: Codable { - - let idStr: String? - let indices: [Int]? - let mediaURL: String? - let httpsMediaURL: String? - let url: String? - let displayURL: String? - let type: String? - let video: TwitterVideo? - - enum CodingKeys: String, CodingKey { - case idStr = "idStr" - case indices = "indices" - case mediaURL = "media_url" - case httpsMediaURL = "media_url_https" - case url = "url" - case displayURL = "display_url" - case type = "type" - case video = "video_info" - } - - func renderAsHTML() -> String { - var html = String() - - switch type { - case "photo": - html += renderPhotoAsHTML() - case "video": - html += renderVideoAsHTML() - case "animated_gif": - html += renderAnimatedGIFAsHTML() - default: - break - } - - return html - } - -} - -private extension TwitterExtendedMedia { - - func renderPhotoAsHTML() -> String { - if let httpsMediaURL = httpsMediaURL { - return "
" - } - if let mediaURL = mediaURL { - return "
" - } - return "" - } - - func renderVideoAsHTML() -> String { - guard let bestVariantURL = findBestVariant()?.url else { return "" } - - var html = "" - return html - } - - func renderAnimatedGIFAsHTML() -> String { - guard let bestVariantURL = findBestVariant()?.url else { return "" } - - var html = "" - return html - } - - func findBestVariant() -> TwitterVideo.Variant? { - var best: TwitterVideo.Variant? = nil - if let variants = video?.variants { - for variant in variants { - if let currentBest = best { - if variant.bitrate ?? 0 > currentBest.bitrate ?? 0 { - best = variant - } - } else { - best = variant - } - } - } - return best - } - -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterFeedProvider.swift deleted file mode 100644 index a2f477385..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ /dev/null @@ -1,485 +0,0 @@ -// -// TwitterFeedProvider.swift -// FeedProvider -// -// Created by Maurice Parker on 4/7/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -import Secrets -import OAuthSwift -import RSParser -import RSWeb - -public enum TwitterFeedProviderError: LocalizedError { - case rateLimitExceeded - case screenNameNotFound - case unknown - - public var errorDescription: String? { - switch self { - case .rateLimitExceeded: - return NSLocalizedString("Twitter API rate limit has been exceeded. Please wait a short time and try again.", comment: "Rate Limit") - case .screenNameNotFound: - return NSLocalizedString("Unable to determine screen name.", comment: "Screen name") - case .unknown: - return NSLocalizedString("An unknown Twitter Feed Provider error has occurred.", comment: "Unknown error") - } - } -} - -public enum TwitterFeedType: Int { - case homeTimeline = 0 - case mentions = 1 - case screenName = 2 - case search = 3 -} - -public final class TwitterFeedProvider: FeedProvider { - - private static let homeURL = "https://www.twitter.com" - private static let iconURL = "https://abs.twimg.com/favicons/twitter.ico" - private static let server = "api.twitter.com" - private static let apiBase = "https://api.twitter.com/1.1/" - private static let userAgentHeaders = UserAgent.headers() as! [String: String] - private static let dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" - - private static let userPaths = ["/", "/home", "/notifications"] - private static let reservedPaths = ["/search", "/explore", "/messages", "/i", "/compose", "/notifications/mentions"] - - private var parsingQueue = DispatchQueue(label: "TwitterFeedProvider parse queue") - - public var screenName: String - - private var oauthToken: String - private var oauthTokenSecret: String - - private var client: OAuthSwiftClient - - private var rateLimitRemaining: Int? - private var rateLimitReset: Date? - - public init?(tokenSuccess: OAuthSwift.TokenSuccess) { - guard let screenName = tokenSuccess.parameters["screen_name"] as? String else { - return nil - } - - self.screenName = screenName - self.oauthToken = tokenSuccess.credential.oauthToken - self.oauthTokenSecret = tokenSuccess.credential.oauthTokenSecret - - let tokenCredentials = Credentials(type: .oauthAccessToken, username: screenName, secret: oauthToken) - try? CredentialsManager.storeCredentials(tokenCredentials, server: Self.server) - - let tokenSecretCredentials = Credentials(type: .oauthAccessTokenSecret, username: screenName, secret: oauthTokenSecret) - try? CredentialsManager.storeCredentials(tokenSecretCredentials, server: Self.server) - - client = OAuthSwiftClient(consumerKey: SecretsManager.provider.twitterConsumerKey, - consumerSecret: SecretsManager.provider.twitterConsumerSecret, - oauthToken: oauthToken, - oauthTokenSecret: oauthTokenSecret, - version: .oauth1) - } - - public init?(screenName: String) { - self.screenName = screenName - - guard let tokenCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessToken, server: Self.server, username: screenName), - let tokenSecretCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessTokenSecret, server: Self.server, username: screenName) else { - return nil - } - - self.oauthToken = tokenCredentials.secret - self.oauthTokenSecret = tokenSecretCredentials.secret - - client = OAuthSwiftClient(consumerKey: SecretsManager.provider.twitterConsumerKey, - consumerSecret: SecretsManager.provider.twitterConsumerSecret, - oauthToken: oauthToken, - oauthTokenSecret: oauthTokenSecret, - version: .oauth1) - } - - public func ability(_ urlComponents: URLComponents) -> FeedProviderAbility { - guard urlComponents.host?.hasSuffix("twitter.com") ?? false else { - return .none - } - - if let username = urlComponents.user { - if username == screenName { - return .owner - } else { - return .none - } - } - - return .available - } - - public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { - if let screenName = deriveScreenName(urlComponents) { - retrieveUser(screenName: screenName) { result in - switch result { - case .success(let user): - if let avatarURL = user.avatarURL { - completion(.success(avatarURL)) - } else { - completion(.failure(TwitterFeedProviderError.screenNameNotFound)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } else { - completion(.success(Self.iconURL)) - } - } - - public func metaData(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { - switch urlComponents.path { - - case "", "/", "/home": - let name = NSLocalizedString("Twitter Timeline", comment: "Twitter Timeline") - completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL))) - - case "/notifications/mentions": - let name = NSLocalizedString("Twitter Mentions", comment: "Twitter Mentions") - completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL))) - - case "/search": - if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value { - let localized = NSLocalizedString("Twitter Search: %@", comment: "Twitter Search") - let name = NSString.localizedStringWithFormat(localized as NSString, query) as String - completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL))) - } else { - let name = NSLocalizedString("Twitter Search", comment: "Twitter Search") - completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL))) - } - - default: - if let hashtag = deriveHashtag(urlComponents) { - completion(.success(FeedProviderFeedMetaData(name: "#\(hashtag)", homePageURL: Self.homeURL))) - } else if let listID = deriveListID(urlComponents) { - retrieveList(listID: listID) { result in - switch result { - case .success(let list): - if let userName = list.name { - var urlComponents = URLComponents(string: Self.homeURL) - urlComponents?.path = "/i/lists/\(listID)" - completion(.success(FeedProviderFeedMetaData(name: userName, homePageURL: urlComponents?.url?.absoluteString))) - } else { - completion(.failure(TwitterFeedProviderError.screenNameNotFound)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } else if let screenName = deriveScreenName(urlComponents) { - retrieveUser(screenName: screenName) { result in - switch result { - case .success(let user): - if let userName = user.name { - var urlComponents = URLComponents(string: Self.homeURL) - urlComponents?.path = "/\(screenName)" - completion(.success(FeedProviderFeedMetaData(name: userName, homePageURL: urlComponents?.url?.absoluteString))) - } else { - completion(.failure(TwitterFeedProviderError.screenNameNotFound)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } else { - completion(.failure(TwitterFeedProviderError.unknown)) - } - - } - } - - public func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) { - guard let urlComponents = URLComponents(string: webFeed.url) else { - completion(.failure(TwitterFeedProviderError.unknown)) - return - } - - let api: String - var parameters = [String: Any]() - var isSearch = false - - parameters["count"] = 20 - - switch urlComponents.path { - case "", "/", "/home": - parameters["count"] = 100 - if let sinceToken = webFeed.sinceToken, let sinceID = Int(sinceToken) { - parameters["since_id"] = sinceID - } - api = "statuses/home_timeline.json" - case "/notifications/mentions": - api = "statuses/mentions_timeline.json" - case "/search": - api = "search/tweets.json" - if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value { - parameters["q"] = query - } - isSearch = true - default: - if let hashtag = deriveHashtag(urlComponents) { - api = "search/tweets.json" - parameters["q"] = "#\(hashtag)" - isSearch = true - } else if let listID = deriveListID(urlComponents) { - api = "lists/statuses.json" - parameters["list_id"] = listID - } else if let screenName = deriveScreenName(urlComponents) { - api = "statuses/user_timeline.json" - parameters["exclude_replies"] = true - parameters["screen_name"] = screenName - } else { - completion(.failure(TwitterFeedProviderError.unknown)) - return - } - } - - retrieveTweets(api: api, parameters: parameters, isSearch: isSearch) { result in - switch result { - case .success(let statuses): - if let sinceID = statuses.first?.idStr { - webFeed.sinceToken = sinceID - } - self.parsingQueue.async { - let parsedItems = self.makeParsedItems(webFeed.url, statuses) - DispatchQueue.main.async { - completion(.success(parsedItems)) - } - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public static func buildURL(_ type: TwitterFeedType, username: String?, screenName: String?, searchField: String?) -> URL? { - var components = URLComponents() - components.scheme = "https" - components.host = "twitter.com" - - switch type { - case .homeTimeline: - guard let username = username else { - return nil - } - components.user = username - case .mentions: - guard let username = username else { - return nil - } - components.user = username - components.path = "/notifications/mentions" - case .screenName: - guard let screenName = screenName else { - return nil - } - components.path = "/\(screenName)" - case .search: - guard let searchField = searchField else { - return nil - } - components.path = "/search" - components.queryItems = [URLQueryItem(name: "q", value: searchField)] - } - - return components.url - } - -} - -// MARK: OAuth1SwiftProvider - -extension TwitterFeedProvider: OAuth1SwiftProvider { - - public static var callbackURL: URL { - return URL(string: "netnewswire://")! - } - - public static var oauth1Swift: OAuth1Swift { - return OAuth1Swift( - consumerKey: SecretsManager.provider.twitterConsumerKey, - consumerSecret: SecretsManager.provider.twitterConsumerSecret, - requestTokenUrl: "https://api.twitter.com/oauth/request_token", - authorizeUrl: "https://api.twitter.com/oauth/authorize", - accessTokenUrl: "https://api.twitter.com/oauth/access_token" - ) - } - -} - -// MARK: Private - -private extension TwitterFeedProvider { - - func deriveHashtag(_ urlComponents: URLComponents) -> String? { - let path = urlComponents.path - if path.starts(with: "/hashtag/"), let startIndex = path.index(path.startIndex, offsetBy: 9, limitedBy: path.endIndex), startIndex < path.endIndex { - return String(path[startIndex.. String? { - let path = urlComponents.path - guard !Self.reservedPaths.contains(path) else { return nil } - - if path.isEmpty || Self.userPaths.contains(path) { - return screenName - } else { - return String(path.suffix(from: path.index(path.startIndex, offsetBy: 1))) - } - } - - func deriveListID(_ urlComponents: URLComponents) -> String? { - let path = urlComponents.path - guard path.starts(with: "/i/lists/") else { return nil } - return String(path.suffix(from: path.index(path.startIndex, offsetBy: 9))) - } - - func retrieveUser(screenName: String, completion: @escaping (Result) -> Void) { - let url = "\(Self.apiBase)users/show.json" - let parameters = ["screen_name": screenName] - - client.get(url, parameters: parameters, headers: Self.userAgentHeaders) { result in - switch result { - case .success(let response): - let decoder = JSONDecoder() - do { - let user = try decoder.decode(TwitterUser.self, from: response.data) - completion(.success(user)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - func retrieveList(listID: String, completion: @escaping (Result) -> Void) { - let url = "\(Self.apiBase)lists/show.json" - let parameters = ["list_id": listID] - - client.get(url, parameters: parameters, headers: Self.userAgentHeaders) { result in - switch result { - case .success(let response): - let decoder = JSONDecoder() - do { - let collection = try decoder.decode(TwitterList.self, from: response.data) - completion(.success(collection)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - func retrieveTweets(api: String, parameters: [String: Any], isSearch: Bool, completion: @escaping (Result<[TwitterStatus], Error>) -> Void) { - let url = "\(Self.apiBase)\(api)" - var expandedParameters = parameters - expandedParameters["tweet_mode"] = "extended" - - if let remaining = rateLimitRemaining, let reset = rateLimitReset, remaining < 1 && reset > Date() { - completion(.failure(TwitterFeedProviderError.rateLimitExceeded)) - return - } - - client.get(url, parameters: expandedParameters, headers: Self.userAgentHeaders) { result in - switch result { - case .success(let response): - - let decoder = JSONDecoder() - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale.init(identifier: "en_US_POSIX") - dateFormatter.dateFormat = Self.dateFormat - decoder.dateDecodingStrategy = .formatted(dateFormatter) - - if let remaining = response.response.value(forHTTPHeaderField: "x-rate-limit-remaining") { - self.rateLimitRemaining = Int(remaining) ?? 0 - } - if let reset = response.response.value(forHTTPHeaderField: "x-rate-limit-reset") { - self.rateLimitReset = Date(timeIntervalSince1970: Double(reset) ?? 0) - } - - self.parsingQueue.async { - do { - let tweets: [TwitterStatus] - if isSearch { - let searchResult = try decoder.decode(TwitterSearchResult.self, from: response.data) - if let statuses = searchResult.statuses { - tweets = statuses - } else { - tweets = [TwitterStatus]() - } - } else { - tweets = try decoder.decode([TwitterStatus].self, from: response.data) - } - DispatchQueue.main.async { - completion(.success(tweets)) - } - } catch { - DispatchQueue.main.async { - completion(.failure(error)) - } - } - } - - case .failure(let error): - if error.errorCode == -11 { - // Eat these errors. They are old or invalid URL requests. - completion(.success([TwitterStatus]())) - } else { - completion(.failure(error)) - } - } - } - } - - func makeParsedItems(_ webFeedURL: String, _ statuses: [TwitterStatus]) -> Set { - var parsedItems = Set() - - for status in statuses { - guard let idStr = status.idStr, let statusURL = status.url else { continue } - - let parsedItem = ParsedItem(syncServiceID: nil, - uniqueID: idStr, - feedURL: webFeedURL, - url: statusURL, - externalURL: nil, - title: nil, - language: nil, - contentHTML: status.renderAsHTML(), - contentText: status.renderAsText(), - summary: nil, - imageURL: nil, - bannerImageURL: nil, - datePublished: status.createdAt, - dateModified: nil, - authors: makeParsedAuthors(status.user), - tags: nil, - attachments: nil) - parsedItems.insert(parsedItem) - } - - return parsedItems - } - - func makeUserURL(_ screenName: String) -> String { - return "https://twitter.com/\(screenName)" - } - - func makeParsedAuthors(_ user: TwitterUser?) -> Set? { - guard let user = user else { return nil } - return Set([ParsedAuthor(name: user.name, url: user.url, avatarURL: user.avatarURL, emailAddress: nil)]) - } - -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterHashtag.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterHashtag.swift deleted file mode 100644 index 029f56c43..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterHashtag.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// TwitterHashtag.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterHashtag: Codable, TwitterEntity { - - let text: String? - let indices: [Int]? - - enum CodingKeys: String, CodingKey { - case text = "text" - case indices = "indices" - } - - func renderAsHTML() -> String { - var html = String() - if let text = text { - html += "#\(text)" - } - return html - } -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterList.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterList.swift deleted file mode 100644 index 96427fe94..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterList.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// TwitterList.swift -// -// -// Created by Maurice Parker on 8/14/20. -// - -import Foundation - -struct TwitterList: Codable { - - let name: String? - - enum CodingKeys: String, CodingKey { - case name = "name" - } -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterMedia.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterMedia.swift deleted file mode 100644 index 57f279a19..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterMedia.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// TwitterMedia.swift -// Account -// -// Created by Maurice Parker on 4/20/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterMedia: Codable, TwitterEntity { - - let indices: [Int]? - - enum CodingKeys: String, CodingKey { - case indices = "indices" - } - - func renderAsHTML() -> String { - return String() - } -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterMention.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterMention.swift deleted file mode 100644 index 4cd00551f..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterMention.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// TwitterMention.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterMention: Codable, TwitterEntity { - - let name: String? - let indices: [Int]? - let screenName: String? - let idStr: String? - - enum CodingKeys: String, CodingKey { - case name = "url" - case indices = "indices" - case screenName = "screen_name" - case idStr = "idStr" - } - - func renderAsHTML() -> String { - var html = String() - if let screenName = screenName { - html += "@\(screenName)" - } - return html - } - -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterSearchResult.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterSearchResult.swift deleted file mode 100644 index 6a8a1373f..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterSearchResult.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// TwitterSearchResult.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterSearchResult: Codable { - - let statuses: [TwitterStatus]? - - enum CodingKeys: String, CodingKey { - case statuses = "statuses" - } -} - diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterStatus.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterStatus.swift deleted file mode 100644 index fa89f005c..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterStatus.swift +++ /dev/null @@ -1,169 +0,0 @@ -// -// TwitterStatus.swift -// Account -// -// Created by Maurice Parker on 4/16/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -final class TwitterStatus: Codable { - - let createdAt: Date? - let idStr: String? - let fullText: String? - let displayTextRange: [Int]? - let user: TwitterUser? - let truncated: Bool? - let retweeted: Bool? - let retweetedStatus: TwitterStatus? - let quotedStatus: TwitterStatus? - let entities: TwitterEntities? - let extendedEntities: TwitterExtendedEntities? - - enum CodingKeys: String, CodingKey { - case createdAt = "created_at" - case idStr = "id_str" - case fullText = "full_text" - case displayTextRange = "display_text_range" - case user = "user" - case truncated = "truncated" - case retweeted = "retweeted" - case retweetedStatus = "retweeted_status" - case quotedStatus = "quoted_status" - case entities = "entities" - case extendedEntities = "extended_entities" - } - - var url: String? { - guard let userURL = user?.url, let idStr = idStr else { return nil } - return "\(userURL)/status/\(idStr)" - } - - func renderAsText() -> String? { - let statusToRender = retweetedStatus != nil ? retweetedStatus! : self - return statusToRender.displayText - } - - func renderAsHTML(topLevel: Bool = true) -> String { - if let retweetedStatus = retweetedStatus { - return renderAsRetweetHTML(retweetedStatus) - } - if let quotedStatus = quotedStatus { - return renderAsQuoteHTML(quotedStatus, topLevel: topLevel) - } - return renderAsOriginalHTML(topLevel: topLevel) - } - -} - -private extension TwitterStatus { - - var displayText: String? { - if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1, - let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), - let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) { - return String(text[startIndex.. 1, let entities = entities?.combineAndSort() { - - let displayStartIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex) ?? text.startIndex - let displayEndIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) ?? text.endIndex - - var html = String() - var prevIndex = displayStartIndex - var unicodeScalarOffset = 0 - - for entity in entities { - - // 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..") - } - - // We drop off any URL which is just pointing to the quoted status. It is redundant. - if let twitterURL = entity as? TwitterURL, let expandedURL = twitterURL.expandedURL, let quotedURL = quotedStatus?.url { - if expandedURL.caseInsensitiveCompare(quotedURL) != .orderedSame { - html += entity.renderAsHTML() - } - } else { - html += entity.renderAsHTML() - } - - prevIndex = entityEndIndex - - } - - if prevIndex < displayEndIndex { - html += String(text[prevIndex..") - } - - return html - } else { - return displayText - } - } - - func renderAsTweetHTML(_ status: TwitterStatus, topLevel: Bool) -> String { - var html = "
\(status.displayHTML ?? "")
" - - if !topLevel, let createdAt = status.createdAt, let url = status.url { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .medium - dateFormatter.timeStyle = .short - html += "\(dateFormatter.string(from: createdAt))" - } - - return html - } - - func renderAsOriginalHTML(topLevel: Bool) -> String { - var html = renderAsTweetHTML(self, topLevel: topLevel) - html += extendedEntities?.renderAsHTML() ?? "" - return html - } - - func renderAsRetweetHTML(_ status: TwitterStatus) -> String { - var html = "
" - if let userHTML = status.user?.renderAsHTML() { - html += userHTML - } - html += status.renderAsHTML(topLevel: false) - html += "
" - return html - } - - func renderAsQuoteHTML(_ quotedStatus: TwitterStatus, topLevel: Bool) -> String { - var html = String() - html += renderAsTweetHTML(self, topLevel: topLevel) - html += extendedEntities?.renderAsHTML() ?? "" - html += "
" - if let userHTML = quotedStatus.user?.renderAsHTML() { - html += userHTML - } - html += quotedStatus.renderAsHTML(topLevel: false) - html += "
" - return html - } - -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterSymbol.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterSymbol.swift deleted file mode 100644 index 9b1119743..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterSymbol.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// TwitterSymbol.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterSymbol: Codable, TwitterEntity { - - let text: String? - let indices: [Int]? - - enum CodingKeys: String, CodingKey { - case text = "text" - case indices = "indices" - } - - func renderAsHTML() -> String { - var html = String() - if let text = text { - html += "$\(text)" - } - return html - } - -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterURL.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterURL.swift deleted file mode 100644 index d7f80c6b6..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterURL.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// TwitterURL.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterURL: Codable, TwitterEntity { - - let url: String? - let indices: [Int]? - let displayURL: String? - let expandedURL: String? - - enum CodingKeys: String, CodingKey { - case url = "url" - case indices = "indices" - case displayURL = "display_url" - case expandedURL = "expanded_url" - } - - func renderAsHTML() -> String { - var html = String() - if let expandedURL = expandedURL, let displayURL = displayURL { - html += "\(displayURL)" - } - return html - } - -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterUser.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterUser.swift deleted file mode 100644 index e87b21bcb..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterUser.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// TwitterUser.swift -// Account -// -// Created by Maurice Parker on 4/16/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterUser: Codable { - - let name: String? - let screenName: String? - let avatarURL: String? - - enum CodingKeys: String, CodingKey { - case name = "name" - case screenName = "screen_name" - case avatarURL = "profile_image_url_https" - } - - var url: String { - return "https://twitter.com/\(screenName ?? "")" - } - - func renderAsHTML() -> String? { - var html = String() - html += "" - return html - } - -} diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterVideo.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterVideo.swift deleted file mode 100644 index a6df96292..000000000 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterVideo.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// TwitterVideoInfo.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - - -struct TwitterVideo: Codable { - - let variants: [Variant]? - - enum CodingKeys: String, CodingKey { - case variants = "variants" - } - - struct Variant: Codable { - - let bitrate: Int? - let contentType: String? - let url: String? - - enum CodingKeys: String, CodingKey { - case bitrate = "bitrate" - case contentType = "content_type" - case url = "url" - } - - } - -} diff --git a/Account/Sources/Account/WebFeed.swift b/Account/Sources/Account/WebFeed.swift index f74ec904f..f1ac92d82 100644 --- a/Account/Sources/Account/WebFeed.swift +++ b/Account/Sources/Account/WebFeed.swift @@ -253,17 +253,13 @@ public final class WebFeed: Feed, Renamable, Hashable, ObservableObject { // MARK: - NotificationDisplayName public var notificationDisplayName: String { #if os(macOS) - if self.url.contains("twitter.com") { - return NSLocalizedString("Show notifications for new tweets", comment: "notifyNameDisplay / Twitter") - } else if self.url.contains("www.reddit.com") { + if self.url.contains("www.reddit.com") { return NSLocalizedString("Show notifications for new posts", comment: "notifyNameDisplay / Reddit") } else { return NSLocalizedString("Show notifications for new articles", comment: "notifyNameDisplay / Default") } #else - if self.url.contains("twitter.com") { - return NSLocalizedString("Notify about new tweets", comment: "notifyNameDisplay / Twitter") - } else if self.url.contains("www.reddit.com") { + if self.url.contains("www.reddit.com") { return NSLocalizedString("Notify about new posts", comment: "notifyNameDisplay / Reddit") } else { return NSLocalizedString("Notify about new articles", comment: "notifyNameDisplay / Default") diff --git a/Account/Tests/AccountTests/Feedly/FeedlyTestSecrets.swift b/Account/Tests/AccountTests/Feedly/FeedlyTestSecrets.swift index 237540e8e..490772d2e 100644 --- a/Account/Tests/AccountTests/Feedly/FeedlyTestSecrets.swift +++ b/Account/Tests/AccountTests/Feedly/FeedlyTestSecrets.swift @@ -13,8 +13,6 @@ struct FeedlyTestSecrets: SecretsProvider { var mercuryClientSecret = "" var feedlyClientId = "" var feedlyClientSecret = "" - var twitterConsumerKey = "" - var twitterConsumerSecret = "" var redditConsumerKey = "" var inoreaderAppId = "" var inoreaderAppKey = "" diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index ad9ed9519..d732f28d8 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -84,10 +84,6 @@ struct AppAssets { return RSImage(named: "extensionPointReddit")! }() - static var extensionPointTwitter: RSImage = { - return RSImage(named: "extensionPointTwitter")! - }() - static var faviconTemplateImage: RSImage = { return RSImage(named: "faviconTemplateImage")! }() diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index 685d6fd44..66d952b7d 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -43,6 +43,7 @@ final class AppDefaults { static let defaultBrowserID = "defaultBrowserID" static let currentThemeName = "currentThemeName" static let hasSeenNotAllArticlesHaveURLsAlert = "hasSeenNotAllArticlesHaveURLsAlert" + static let twitterDeprecationAlertShown = "twitterDeprecationAlertShown" // Hidden prefs static let showDebugMenu = "ShowDebugMenu" @@ -318,6 +319,16 @@ final class AppDefaults { UserDefaults.standard.set(newValue.rawValue, forKey: Key.refreshInterval) } } + + var twitterDeprecationAlertShown: Bool { + get { + return AppDefaults.bool(for: Key.twitterDeprecationAlertShown) + } + set { + AppDefaults.setBool(for: Key.twitterDeprecationAlertShown, newValue) + } + } + func registerDefaults() { #if DEBUG diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 0d2d0bae6..97923bdae 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -131,6 +131,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(didWakeNotification(_:)), name: NSWorkspace.didWakeNotification, object: nil) appDelegate = self + + presentTwitterDeprecationAlertIfRequired() } // MARK: - API @@ -520,26 +522,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, return ExtensionPointManager.shared.isRedditEnabled } - if item.action == #selector(showAddTwitterFeedWindow(_:)) { - guard !isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager.shared.isTwitterEnabled else { - return false - } - return ExtensionPointManager.shared.isTwitterEnabled - } - - #if !DEBUG - if item.action == #selector(debugDropConditionalGetInfo(_:)) { - return false - } - #endif - - if item.action == #selector(debugTestCrashReporterWindow(_:)) || - item.action == #selector(debugTestCrashReportSending(_:)) || - item.action == #selector(forceCrash(_:)) { - let appIDPrefix = Bundle.main.infoDictionary?["AppIdentifierPrefix"] as! String - return appIDPrefix == "M8L2WTLA8W." - } - #if !MAC_APP_STORE if item.action == #selector(toggleWebInspectorEnabled(_:)) { (item as! NSMenuItem).state = AppDefaults.shared.webInspectorEnabled ? .on : .off @@ -619,12 +601,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, addFeedController?.showAddFeedSheet(.redditFeed) } - @IBAction func showAddTwitterFeedWindow(_ sender: Any?) { - createAndShowMainWindowIfNecessary() - addFeedController = AddFeedController(hostWindow: mainWindowController!.window!) - addFeedController?.showAddFeedSheet(.twitterFeed) - } - @IBAction func showAddFolderWindow(_ sender: Any?) { createAndShowMainWindowIfNecessary() showAddFolderSheetOnWindow(mainWindowController!.window!) @@ -980,6 +956,33 @@ internal extension AppDelegate { alert.beginSheetModal(for: window) } + private func presentTwitterDeprecationAlertIfRequired() { + if AppDefaults.shared.twitterDeprecationAlertShown { return } + + let expiryDate = Date(timeIntervalSince1970: 1691539200) // August 9th 2023, 00:00 UTC + let currentDate = Date() + if currentDate > expiryDate { + return // If after August 9th, don't show + } + + if AccountManager.shared.anyLocalOriCloudAccountHasAtLeastOneTwitterFeed() { + showTwitterDeprecationAlert() + } + AppDefaults.shared.twitterDeprecationAlertShown = true + } + + private func showTwitterDeprecationAlert() { + DispatchQueue.main.async { + let alert = NSAlert() + alert.alertStyle = .warning + alert.messageText = NSLocalizedString("Twitter Integration Removed", comment: "Twitter Integration Removed") + alert.informativeText = NSLocalizedString("On February 1, 2023, Twitter announced the end of free access to the Twitter API, effective February 9.\n\nSince Twitter does not provide RSS feeds, we’ve had to use the Twitter API. Without free access to that API, we can’t read feeds from Twitter.\n\nWe’ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’t delete those feeds.\n\nYou can still read whatever you have already downloaded. However, those feeds will no longer update.", comment: "Twitter deprecation informative text.") + alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK")) + alert.buttons[0].keyEquivalent = "\r" + alert.runModal() + } + } + } /* diff --git a/Mac/Base.lproj/AddTwitterFeedSheet.xib b/Mac/Base.lproj/AddTwitterFeedSheet.xib deleted file mode 100644 index d449a42eb..000000000 --- a/Mac/Base.lproj/AddTwitterFeedSheet.xib +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Mac/Base.lproj/Main.storyboard b/Mac/Base.lproj/Main.storyboard index 46b22d53b..50342563a 100644 --- a/Mac/Base.lproj/Main.storyboard +++ b/Mac/Base.lproj/Main.storyboard @@ -80,12 +80,6 @@ - - - - - - diff --git a/Mac/MainWindow/AddFeed/AddFeedController.swift b/Mac/MainWindow/AddFeed/AddFeedController.swift index 0530a980a..d4f333af0 100644 --- a/Mac/MainWindow/AddFeed/AddFeedController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedController.swift @@ -48,9 +48,6 @@ class AddFeedController: AddFeedWindowControllerDelegate { case .redditFeed: addFeedWindowController = AddRedditFeedWindowController(folderTreeController: folderTreeController, delegate: self) - case .twitterFeed: - addFeedWindowController = AddTwitterFeedWindowController(folderTreeController: folderTreeController, - delegate: self) } addFeedWindowController!.runSheetOnWindow(hostWindow) diff --git a/Mac/MainWindow/AddFeed/AddFeedWIndowController.swift b/Mac/MainWindow/AddFeed/AddFeedWIndowController.swift index 1651ec79f..e574d7fa5 100644 --- a/Mac/MainWindow/AddFeed/AddFeedWIndowController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedWIndowController.swift @@ -12,7 +12,6 @@ import Account enum AddFeedWindowControllerType { case webFeed case redditFeed - case twitterFeed } protocol AddFeedWindowControllerDelegate: AnyObject { diff --git a/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift deleted file mode 100644 index 1d5ed0d5f..000000000 --- a/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift +++ /dev/null @@ -1,196 +0,0 @@ -// -// AddTwitterFeedWindowController.swift -// NetNewsWire -// -// Created by Maurice Parker on 4/21/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import AppKit -import RSCore -import RSTree -import Articles -import Account - -class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowController { - - @IBOutlet weak var typePopupButton: NSPopUpButton! - @IBOutlet weak var typeDescriptionLabel: NSTextField! - - @IBOutlet weak var accountLabel: NSTextField! - @IBOutlet weak var accountPopupButton: NSPopUpButton! - @IBOutlet weak var screenSearchTextField: NSTextField! - - @IBOutlet var nameTextField: NSTextField! - @IBOutlet var addButton: NSButton! - @IBOutlet var folderPopupButton: NSPopUpButton! - - private weak var delegate: AddFeedWindowControllerDelegate? - private var folderTreeController: TreeController! - - private var userEnteredScreenSearch: String? { - var s = screenSearchTextField.stringValue - s = s.collapsingWhitespace - if s.isEmpty { - return nil - } - return s - } - - private var userEnteredTitle: String? { - var s = nameTextField.stringValue - s = s.collapsingWhitespace - if s.isEmpty { - return nil - } - return s - } - - var hostWindow: NSWindow! - - convenience init(folderTreeController: TreeController, delegate: AddFeedWindowControllerDelegate?) { - self.init(windowNibName: NSNib.Name("AddTwitterFeedSheet")) - self.folderTreeController = folderTreeController - self.delegate = delegate - } - - func runSheetOnWindow(_ hostWindow: NSWindow) { - hostWindow.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) -> Void in - } - } - - override func windowDidLoad() { - - let accountMenu = NSMenu() - for feedProvider in ExtensionPointManager.shared.activeFeedProviders { - if let twitterFeedProvider = feedProvider as? TwitterFeedProvider { - let accountMenuItem = NSMenuItem() - accountMenuItem.title = "@\(twitterFeedProvider.screenName)" - accountMenu.addItem(accountMenuItem) - } - } - accountPopupButton.menu = accountMenu - - folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode, restrictToSpecialAccounts: true) - - if let container = AddWebFeedDefaultContainer.defaultContainer { - if let folder = container as? Folder, let account = folder.account { - FolderTreeMenu.select(account: account, folder: folder, in: folderPopupButton) - } else { - if let account = container as? Account { - FolderTreeMenu.select(account: account, folder: nil, in: folderPopupButton) - } - } - } - - updateUI() - } - - // MARK: Actions - - @IBAction func selectedType(_ sender: Any) { - screenSearchTextField.stringValue = "" - updateUI() - } - - @IBAction func cancel(_ sender: Any?) { - cancelSheet() - } - - @IBAction func addFeed(_ sender: Any?) { - guard let type = TwitterFeedType(rawValue: typePopupButton.selectedItem?.tag ?? 0), - let atUsername = accountPopupButton.selectedItem?.title else { return } - - let username = String(atUsername[atUsername.index(atUsername.startIndex, offsetBy: 1).. Container? { - return folderPopupButton.selectedItem?.representedObject as? Container - } -} diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index d518e6df5..cebdf3e50 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -1452,11 +1452,6 @@ private extension MainWindowController { newRedditFeedItem.action = Selector(("showAddRedditFeedWindow:")) menu.addItem(newRedditFeedItem) - let newTwitterFeedItem = NSMenuItem() - newTwitterFeedItem.title = NSLocalizedString("button.title.new-twitter-feed", comment: "New Twitter Feed...") - newTwitterFeedItem.action = Selector(("showAddTwitterFeedWindow:")) - menu.addItem(newTwitterFeedItem) - let newFolderFeedItem = NSMenuItem() newFolderFeedItem.title = NSLocalizedString("button.title.open-new-folder", comment: "New Folder...") newFolderFeedItem.action = Selector(("showAddFolderWindow:")) diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 8dbd08563..0d571f8ed 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -376,7 +376,10 @@ protocol SidebarDelegate: AnyObject { } func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool { - let node = item as! Node + guard let node = item as? Node else { + assertionFailure("Expected item to be a Node.") + return false + } return node.isGroupItem } diff --git a/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json b/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json deleted file mode 100644 index 37bb7c41d..000000000 --- a/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "twitter24x24.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "twitter48x48.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter24x24.png b/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter24x24.png deleted file mode 100644 index 4d26fe41f..000000000 Binary files a/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter24x24.png and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter48x48.png b/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter48x48.png deleted file mode 100644 index a396124f5..000000000 Binary files a/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter48x48.png and /dev/null differ diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index b9d58ccf1..374f61fe6 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -117,10 +117,6 @@ 512392C024E33A3C00F11704 /* RedditAdd.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516AE5FF246AF34100731738 /* RedditAdd.storyboard */; }; 512392C124E33A3C00F11704 /* RedditSelectTypeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE601246AF36100731738 /* RedditSelectTypeTableViewController.swift */; }; 512392C224E33A3C00F11704 /* RedditEnterDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE605246AF3A900731738 /* RedditEnterDetailTableViewController.swift */; }; - 512392C324E3451400F11704 /* TwitterAdd.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 510289CF2451BA3A00426DDF /* TwitterAdd.storyboard */; }; - 512392C424E3451400F11704 /* TwitterSelectTypeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510289D12451BC1F00426DDF /* TwitterSelectTypeTableViewController.swift */; }; - 512392C524E3451400F11704 /* TwitterEnterDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */; }; - 512392C624E3451400F11704 /* TwitterSelectAccountTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510289D52451DDD100426DDF /* TwitterSelectAccountTableViewController.swift */; }; 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */; }; 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; @@ -197,10 +193,6 @@ 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */; }; 5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */; }; - 514A89A2244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */; }; - 514A89A3244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */; }; - 514A89A5244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */; }; - 514A89A6244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */; }; 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; }; 514C16CE24D2E63F009A3AFA /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16CD24D2E63F009A3AFA /* Account */; }; 514C16DE24D2EF15009A3AFA /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16DD24D2EF15009A3AFA /* RSTree */; }; @@ -210,8 +202,6 @@ 5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; 515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; 515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; - 515A5107243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; }; - 515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; }; 515A5148243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */; }; 515A5149243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */; }; 515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */; }; @@ -222,7 +212,6 @@ 515A5178243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; 515A517B243E90260089E588 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; 515A517C243E90260089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; - 515A5180243E90260089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; }; 515A5181243E90260089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; @@ -1197,9 +1186,6 @@ 17E0084525941887000C23F0 /* SizeCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeCategories.swift; sourceTree = ""; }; 49F40DEF2335B71000552BF4 /* newsfoot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newsfoot.js; sourceTree = ""; }; 510289CC24519A1D00426DDF /* SelectComboTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectComboTableViewCell.swift; sourceTree = ""; }; - 510289CF2451BA3A00426DDF /* TwitterAdd.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TwitterAdd.storyboard; sourceTree = ""; }; - 510289D12451BC1F00426DDF /* TwitterSelectTypeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterSelectTypeTableViewController.swift; sourceTree = ""; }; - 510289D52451DDD100426DDF /* TwitterSelectAccountTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterSelectAccountTableViewController.swift; sourceTree = ""; }; 5103A9972421643300410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; 5103A9B324216A4200410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; 5103A9DA242258C600410853 /* AccountsAddCloudKit.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsAddCloudKit.xib; sourceTree = ""; }; @@ -1261,11 +1247,8 @@ 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = ""; }; 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MasterTimelineTitleView.xib; sourceTree = ""; }; 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineTitleView.swift; sourceTree = ""; }; - 514A8980244FD63F0085E65D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddTwitterFeedSheet.xib; sourceTree = SOURCE_ROOT; }; - 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AddTwitterFeedWindowController.swift; path = AddFeed/AddTwitterFeedWindowController.swift; sourceTree = ""; }; 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; }; 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = ""; }; - 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider-Extensions.swift"; sourceTree = ""; }; 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointEnableWindowController.swift; sourceTree = ""; }; 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointDetail.xib; sourceTree = ""; }; 515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointDetailViewController.swift; sourceTree = ""; }; @@ -1326,7 +1309,6 @@ 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL-Extensions.swift"; sourceTree = ""; }; - 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterEnterDetailTableViewController.swift; sourceTree = ""; }; 51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuPreviewViewController.swift; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = ""; }; @@ -1891,17 +1873,6 @@ path = Resources; sourceTree = ""; }; - 510289CE2451BA1E00426DDF /* Twitter */ = { - isa = PBXGroup; - children = ( - 510289CF2451BA3A00426DDF /* TwitterAdd.storyboard */, - 510289D12451BC1F00426DDF /* TwitterSelectTypeTableViewController.swift */, - 510289D52451DDD100426DDF /* TwitterSelectAccountTableViewController.swift */, - 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */, - ); - path = Twitter; - sourceTree = ""; - }; 510C415D24E5CDE3008226FD /* ShareExtension */ = { isa = PBXGroup; children = ( @@ -1924,7 +1895,6 @@ 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */, 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */, 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */, - 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */, 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */, ); path = ExtensionPoints; @@ -2237,7 +2207,6 @@ 510289CC24519A1D00426DDF /* SelectComboTableViewCell.swift */, 51E36E8B239D6765006F47A5 /* AddFeedSelectFolderTableViewCell.xib */, 516AE5DD246AF2DD00731738 /* Reddit */, - 510289CE2451BA1E00426DDF /* Twitter */, ); path = Add; sourceTree = ""; @@ -2459,8 +2428,6 @@ 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */, 51333D392468615D00EB5C91 /* AddRedditFeedSheet.xib */, 51333D1524685D2E00EB5C91 /* AddRedditFeedWindowController.swift */, - 514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */, - 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */, 848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */, 849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */, 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */, @@ -3418,9 +3385,7 @@ }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = SHJK2V3AJG; LastSwiftMigration = 1250; - ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 1; @@ -3585,7 +3550,6 @@ 65ED405C235DEF6C0081F399 /* ImportOPMLSheet.xib in Resources */, 51DEE81926FBFF84006DAA56 /* Promenade.nnwtheme in Resources */, 65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */, - 514A89A3244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */, 51D0214726ED617100FF2E0F /* core.css in Resources */, DDF9E1D828EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */, 5103A9F5242258C600410853 /* AccountsAddCloudKit.xib in Resources */, @@ -3641,7 +3605,7 @@ 511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */, 84C9FCA12262A1B300D921D6 /* Main.storyboard in Resources */, 51BB7C312335ACDE008E8144 /* page.html in Resources */, - 512392C324E3451400F11704 /* TwitterAdd.storyboard in Resources */, + 516A093723609A3600EAE89B /* SettingsComboTableViewCell.xib in Resources */, 51077C5A27A86D16000C71DB /* Hyperlegible.nnwtheme in Resources */, DDF9E1D928EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */, DFD86796295D553D0070D62D /* Localizable.strings in Resources */, @@ -3711,7 +3675,6 @@ 49F40DF82335B71000552BF4 /* newsfoot.js in Resources */, 51333D3B2468615D00EB5C91 /* AddRedditFeedSheet.xib in Resources */, BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */, - 514A89A2244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */, 5103A9982421643300410853 /* blank.html in Resources */, DF93DB2B296A319000586C0E /* Localizable.stringsdict in Resources */, 515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */, @@ -3736,7 +3699,7 @@ /* Begin PBXShellScriptBuildPhase section */ 515D50802326D02600EE1167 /* Run Script: Verify No Build Settings */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 8; + buildActionMask = 12; files = ( ); inputFileListPaths = ( @@ -3748,12 +3711,13 @@ ); outputPaths = ( ); - runOnlyForDeploymentPostprocessing = 1; + runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 buildscripts/VerifyNoBS.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n"; + shellScript = "xcrun -sdk macosx swift buildscripts/VerifyNoBS.swift --xcode ${PROJECT_DIR}/${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; }; 5170CA5A279E468000702605 /* Delete Unnecessary Frameworks */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -4083,7 +4047,6 @@ 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, 518C3194237B00DA004D740F /* DetailIconSchemeHandler.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, - 514A89A6244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, 653813182680E152007A082C /* AccountType+Helpers.swift in Sources */, 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */, @@ -4159,7 +4122,6 @@ 65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */, 65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */, 65ED402A235DEF6C0081F399 /* AddWebFeedWindowController.swift in Sources */, - 515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */, 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */, 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */, 178A9F9E2549449F00AB7E9D /* AddAccountsView.swift in Sources */, @@ -4286,7 +4248,6 @@ 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, 51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */, 176813E92564BAE200D98635 /* WidgetDeepLinks.swift in Sources */, - 512392C424E3451400F11704 /* TwitterSelectTypeTableViewController.swift in Sources */, 51B5C87723F22B8200032075 /* ExtensionContainers.swift in Sources */, 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, @@ -4329,19 +4290,16 @@ DFB34997294C4DCB00BC81AD /* LocalizedNetNewsWireError.swift in Sources */, 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 51F9F3F723DF6DB200A314FD /* ArticleIconSchemeHandler.swift in Sources */, - 512392C524E3451400F11704 /* TwitterEnterDetailTableViewController.swift in Sources */, 512AF9C2236ED52C0066F8BE /* ImageHeaderView.swift in Sources */, 512392C124E33A3C00F11704 /* RedditSelectTypeTableViewController.swift in Sources */, 515A5181243E90260089E588 /* ExtensionPointIdentifer.swift in Sources */, DF766FED29377FD9006FBBE2 /* ExtensionsManagementView.swift in Sources */, 51C45290226509C100C03939 /* PseudoFeed.swift in Sources */, - 512392C624E3451400F11704 /* TwitterSelectAccountTableViewController.swift in Sources */, 51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */, 51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */, 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */, DFD406FC291FB63B00C02962 /* SettingsHelpSheets.swift in Sources */, 51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */, - 515A5180243E90260089E588 /* TwitterFeedProvider-Extensions.swift in Sources */, 51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */, 51EF0F7E2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift in Sources */, 512D554423C804DE0023FFFA /* OpenInSafariActivity.swift in Sources */, @@ -4432,7 +4390,6 @@ 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */, 84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */, 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */, - 514A89A5244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */, 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */, 8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */, @@ -4499,7 +4456,6 @@ 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */, 84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */, 845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */, - 515A5107243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */, 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */, DFBB4EAC2951BC0200639228 /* NNWThemeDocument.swift in Sources */, DFC14F1228EA5DC500F6EE86 /* AboutData.swift in Sources */, diff --git a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire MAS.xcscheme b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire MAS.xcscheme index c67864647..e08473d24 100644 --- a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire MAS.xcscheme +++ b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire MAS.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> @@ -45,15 +45,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - diff --git a/Secrets/Sources/Secrets/SecretsProvider.swift b/Secrets/Sources/Secrets/SecretsProvider.swift index 008921d10..09fd49e83 100644 --- a/Secrets/Sources/Secrets/SecretsProvider.swift +++ b/Secrets/Sources/Secrets/SecretsProvider.swift @@ -12,8 +12,6 @@ public protocol SecretsProvider { var mercuryClientSecret: String { get } var feedlyClientId: String { get } var feedlyClientSecret: String { get } - var twitterConsumerKey: String { get } - var twitterConsumerSecret: String { get } var redditConsumerKey: String { get } var inoreaderAppId: String { get } var inoreaderAppKey: String { get } diff --git a/Shared/Article Rendering/stylesheet.css b/Shared/Article Rendering/stylesheet.css index 902be2fa6..ae37b23ca 100644 --- a/Shared/Article Rendering/stylesheet.css +++ b/Shared/Article Rendering/stylesheet.css @@ -286,30 +286,6 @@ blockquote { border-top: 1px solid var(--header-table-border-color); } -/* Twitter */ - -.twitterAvatar { - vertical-align: middle; - border-radius: 4px; - height: 1.7em; - width: 1.7em; -} - -.twitterUsername { - line-height: 1.2; - margin-left: 4px; - display: inline-block; - vertical-align: middle; -} - -.twitterScreenName { - font-size: 66%; -} - -.twitterTimestamp { - font-size: 66%; -} - /* Newsfoot theme for light mode (default) */ .newsfoot-footnote-popover { background: #ccc; diff --git a/Shared/ArticleStyles/ArticleTheme.swift b/Shared/ArticleStyles/ArticleTheme.swift index c99d863a2..afd5a1d80 100644 --- a/Shared/ArticleStyles/ArticleTheme.swift +++ b/Shared/ArticleStyles/ArticleTheme.swift @@ -21,8 +21,8 @@ struct ArticleTheme: Equatable { static let nnwThemeSuffix = ".nnwtheme" // Don't localize the theme names. - private static let defaultThemeName = "Default" - private static let unknownValue = "Unknown" + private static let defaultThemeName = "NetNewsWire" + private static let unknownValue = NSLocalizedString("Unknown", comment: "Unknown Value") let path: String? let template: String? diff --git a/Shared/ArticleStyles/ArticleThemesManager.swift b/Shared/ArticleStyles/ArticleThemesManager.swift index ac5bce563..f97f0e7f0 100644 --- a/Shared/ArticleStyles/ArticleThemesManager.swift +++ b/Shared/ArticleStyles/ArticleThemesManager.swift @@ -80,11 +80,13 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging, Observable } func presentedSubitemDidChange(at url: URL) { - themeNames = buildThemeNames() - do { - currentTheme = try articleThemeWithThemeName(currentThemeName) - } catch { - appDelegate.presentThemeImportError(error) + if url.lastPathComponent.localizedCaseInsensitiveContains("nnwtheme") { + themeNames = buildThemeNames() + do { + currentTheme = try articleThemeWithThemeName(currentThemeName) + } catch { + appDelegate.presentThemeImportError(error) + } } } diff --git a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift index 39217dd81..db2a2d6b0 100644 --- a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift +++ b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift @@ -15,7 +15,6 @@ enum ExtensionPointIdentifer: Hashable { case marsEdit case microblog #endif - case twitter(String) case reddit(String) var extensionPointType: ExtensionPoint.Type { @@ -26,8 +25,6 @@ enum ExtensionPointIdentifer: Hashable { case .microblog: return SendToMicroBlogCommand.self #endif - case .twitter: - return TwitterFeedProvider.self case .reddit: return RedditFeedProvider.self } @@ -45,11 +42,6 @@ enum ExtensionPointIdentifer: Hashable { "type": "microblog" ] #endif - case .twitter(let screenName): - return [ - "type": "twitter", - "screenName": screenName - ] case .reddit(let username): return [ "type": "reddit", @@ -68,9 +60,6 @@ enum ExtensionPointIdentifer: Hashable { case "microblog": self = ExtensionPointIdentifer.microblog #endif - case "twitter": - guard let screenName = userInfo["screenName"] as? String else { return nil } - self = ExtensionPointIdentifer.twitter(screenName) case "reddit": guard let username = userInfo["username"] as? String else { return nil } self = ExtensionPointIdentifer.reddit(username) @@ -87,9 +76,6 @@ enum ExtensionPointIdentifer: Hashable { case .microblog: hasher.combine("microblog") #endif - case .twitter(let screenName): - hasher.combine("twitter") - hasher.combine(screenName) case .reddit(let username): hasher.combine("reddit") hasher.combine(username) diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index 58914716f..1cac7f967 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -69,16 +69,12 @@ final class ExtensionPointManager: FeedProviderManagerDelegate { return activeExtensionPoints.values.compactMap({ return $0 as? FeedProvider }) } - var isTwitterEnabled: Bool { - return activeExtensionPoints.values.contains(where: { $0 is TwitterFeedProvider }) - } - var isRedditEnabled: Bool { return activeExtensionPoints.values.contains(where: { $0 is RedditFeedProvider }) } init() { - possibleExtensionPointTypes = [TwitterFeedProvider.self, RedditFeedProvider.self] + possibleExtensionPointTypes = [RedditFeedProvider.self] loadExtensionPoints() } @@ -121,12 +117,6 @@ private extension ExtensionPointManager { func extensionPoint(for extensionPointType: ExtensionPoint.Type, tokenSuccess: OAuthSwift.TokenSuccess?, completion: @escaping (Result) -> Void) { switch extensionPointType { - case is TwitterFeedProvider.Type: - if let tokenSuccess = tokenSuccess, let twitter = TwitterFeedProvider(tokenSuccess: tokenSuccess) { - completion(.success(twitter)) - } else { - completion(.failure(ExtensionPointManagerError.unableToCreate)) - } case is RedditFeedProvider.Type: if let tokenSuccess = tokenSuccess { RedditFeedProvider.create(tokenSuccess: tokenSuccess) { result in @@ -147,8 +137,6 @@ private extension ExtensionPointManager { func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint? { switch extensionPointID { - case .twitter(let screenName): - return TwitterFeedProvider(screenName: screenName) case .reddit(let username): return RedditFeedProvider(username: username) #if os(macOS) diff --git a/Shared/Importers/DefaultFeeds.opml b/Shared/Importers/DefaultFeeds.opml index f0581edaf..9e809df40 100644 --- a/Shared/Importers/DefaultFeeds.opml +++ b/Shared/Importers/DefaultFeeds.opml @@ -4,6 +4,7 @@ Default Feeds +<<<<<<< HEAD @@ -21,5 +22,17 @@ +======= + + + + + + + + + + +>>>>>>> ios-release diff --git a/Shared/Resources/Sepia.nnwtheme/stylesheet.css b/Shared/Resources/Sepia.nnwtheme/stylesheet.css index b0c81d1d0..3f88f177d 100644 --- a/Shared/Resources/Sepia.nnwtheme/stylesheet.css +++ b/Shared/Resources/Sepia.nnwtheme/stylesheet.css @@ -3,24 +3,26 @@ body { margin-left: auto; margin-right: auto; - word-wrap: break-word; max-width: 44em; - background-color: #FBF0D9; - color: #704214; + background-color: rgb(248, 241, 227); + color: rgb(79, 50, 28); } +a { + text-decoration: none; +} a:hover { - text-decoration: underline; + text-shadow: 0 1px rgba(255, 255, 255, 2); + opacity: 0.8; } .feedlink { - font-weight: bold; } - -.headerTable { - width: 100%; - height: 68px; +#nnwImageIcon { + width: 32px; + height: 32px; + margin-right: 0.2em; } .systemMessage { @@ -37,34 +39,48 @@ a:hover { --header-color: rgba(0, 0, 0, 0.5); --body-code-color: #704214; --system-message-color: #704214; - --feedlink-color: rgba(255, 0, 0, 0.6); + --feedlink-color: #704214; --article-title-color: #704214; --article-date-color: rgba(0, 0, 0, 0.5); --table-cell-border-color: lightgray; - --primary-accent-color: #43350E; - --secondary-accent-color: #43350E; + --primary-accent-color: #43350e; + --secondary-accent-color: #43350e; --block-quote-border-color: rgba(0, 0, 0, 0.3); } -body a, body a:visited, body a * { +body a, +body a:visited, +body a * { color: var(--secondary-accent-color); } - -body .headerTable { +body > header { border-bottom: 1px solid var(--header-table-border-color); - color: var(--header-color); + padding-bottom: 0.5rem; } -body .header { +body > header a, +body > header a:link, +body > header a:visited { color: var(--header-color); } - -body .header a:link, .header a:visited { +body > header .headerTable { + width: 100%; +} +body > header .headerTable td, +body > header .headerTable th { color: var(--header-color); + padding: 0.2em; + border: none; + font-family: sans-serif; + font-size: 0.9rem; +} +body > header .headerTable td.avatar { + width: 33%; } -body code, body pre { +body code, +body pre { color: var(--body-code-color); } @@ -72,15 +88,11 @@ body > .systemMessage { color: var(--system-message-color); } -.headerContainer a:link, .headerContainer a:visited { - text-decoration: none; +.headerContainer a:link, +.headerContainer a:visited { color: var(--feedlink-color); } -.headerContainer a:hover { - text-decoration: underline; -} - .avatar img { border-radius: 4px; } @@ -97,44 +109,35 @@ body > .systemMessage { text-align: start; } -.articleTitle a:link, .articleTitle a:visited { +.articleTitle a:link, +.articleTitle a:visited { text-decoration: none; color: var(--article-title-color); margin-top: 26px; } -.articleTitle a:hover { - text-decoration: underline; -} - .articleDateline { margin-bottom: 5px; font-weight: bold; } -.articleDateline a:link, .articleDateline a:visited { +.articleDateline a:link, +.articleDateline a:visited { text-decoration: none; color: var(--article-date-color); } -.articleDateline a:hover { - text-decoration: underline; -} - .articleDatelineTitle { margin-bottom: 5px; font-weight: bold; } -.articleDatelineTitle a:link, .articleDatelineTitle a:visited { +.articleDatelineTitle a:link, +.articleDatelineTitle a:visited { text-decoration: none; color: var(--article-title-color); } -.articleDatelineTitle a:hover { - text-decoration: underline; -} - .externalLink { margin-bottom: 5px; font-style: italic; @@ -144,14 +147,11 @@ body > .systemMessage { text-overflow: ellipsis; } -.externalLink a:link, .externalLink a:visited { +.externalLink a:link, +.externalLink a:visited { text-decoration: none; } -.externalLink a:hover { - text-decoration: underline; -} - .articleBody { margin-top: 20px; line-height: 1.6em; @@ -177,17 +177,14 @@ pre { line-height: 1.4286em; } -code, pre { +code, +pre { font-family: "SF Mono", Menlo, "Courier New", Courier, monospace; - font-size: 1em; + font-size: 0.85rem; + letter-spacing: -0.027em; -webkit-hyphens: none; } -pre code { - letter-spacing: -.027em; - font-size: 0.9375em; -} - .nnw-overflow { overflow-x: auto; } @@ -209,7 +206,8 @@ pre code { border: none; } -.nnw-overflow td, .nnw-overflow th { +.nnw-overflow td, +.nnw-overflow th { -webkit-hyphens: none; word-break: normal; border: 1px solid var(--table-cell-border-color); @@ -222,7 +220,10 @@ pre code { border-right: none; } -.nnw-overflow :matches(thead, tbody, tfoot):last-child > tr:last-child :matches(td, th) { +.nnw-overflow + :matches(thead, tbody, tfoot):last-child + > tr:last-child + :matches(td, th) { border-bottom: none; } @@ -235,16 +236,16 @@ pre code { border-width: 0; } -img, figure, video, div, object { +img, +figure, +video, +div, +object { max-width: 100%; height: auto !important; margin: 0 auto; } -video { - width: 100% !important; -} - iframe { max-width: 100%; margin: 0 auto; @@ -340,12 +341,12 @@ blockquote { } .newsfoot-footnote-popover-arrow { - background: #FBF0D9; + background: #fbf0d9; border: 1px solid #ccc; } .newsfoot-footnote-popover-inner { - background: #FBF0D9; + background: #fbf0d9; } body a.footnote, @@ -364,7 +365,6 @@ a.footnote:hover, /* iOS Specific */ @supports (-webkit-touch-callout: none) { - body { margin-top: 3px; margin-bottom: 20px; @@ -374,7 +374,7 @@ a.footnote:hover, word-break: break-word; -webkit-hyphens: auto; -webkit-text-size-adjust: none; - font: Georgia; + font-family: Charter, Georgia, sans-serif; font-size: [[font-size]]px; } @@ -386,18 +386,16 @@ a.footnote:hover, .nnw-overflow table { border: 1px solid var(--secondary-accent-color); } - } /* macOS Specific */ @supports not (-webkit-touch-callout: none) { - body { margin-top: 20px; margin-bottom: 64px; padding-left: 48px; padding-right: 48px; - font-family: Georgia; + font-family: Charter, Georgia, sans-serif; } .smallText { @@ -428,5 +426,4 @@ a.footnote:hover, .nnw-overflow table { border: 1px solid var(--primary-accent-color); } - -} \ No newline at end of file +} diff --git a/Shared/Resources/Sepia.nnwtheme/template.html b/Shared/Resources/Sepia.nnwtheme/template.html index 2b6461013..d77e90199 100644 --- a/Shared/Resources/Sepia.nnwtheme/template.html +++ b/Shared/Resources/Sepia.nnwtheme/template.html @@ -1,8 +1,8 @@
- +
- - + +
[[feed_link_title]]
[[byline]]
[[feed_link_title]]
[[byline]]
diff --git a/Shared/Secrets.swift.gyb b/Shared/Secrets.swift.gyb index 328919ad1..5b7814cb1 100644 --- a/Shared/Secrets.swift.gyb +++ b/Shared/Secrets.swift.gyb @@ -2,7 +2,15 @@ %{ import os +<<<<<<< HEAD +<<<<<<< HEAD secrets = ['MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'TWITTER_CONSUMER_KEY', 'TWITTER_CONSUMER_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY'] +======= +secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY'] +>>>>>>> mac-release +======= +secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY'] +>>>>>>> ios-release def chunks(seq, size): return (seq[i:(i + size)] for i in range(0, len(seq), size)) diff --git a/Shared/Widget/WidgetDataEncoder.swift b/Shared/Widget/WidgetDataEncoder.swift index ac4ba2768..c624e725e 100644 --- a/Shared/Widget/WidgetDataEncoder.swift +++ b/Shared/Widget/WidgetDataEncoder.swift @@ -69,7 +69,7 @@ public final class WidgetDataEncoder { @available(iOS 14, *) private func encodeWidgetData(completion: @escaping (WidgetData?) -> Void) { - var dispatchGroup = DispatchGroup() + let dispatchGroup = DispatchGroup() var groupError: Error? = nil var unread = [LatestArticle]() diff --git a/Technotes/Dependencies.markdown b/Technotes/Dependencies.markdown index 88bb60df8..480e97958 100644 --- a/Technotes/Dependencies.markdown +++ b/Technotes/Dependencies.markdown @@ -20,7 +20,7 @@ RSDatabase uses FMDB for SQLite persistence. Required by OAuthSwift as a testing dependency. Not shipped in NNW. ## [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) -Our Reddit and Twitter use the OAuth framework to authenticate with the services +Our Reddit integration uses the OAuth framework to authenticate with the services and then service requests to them. ## [PLCrashReporter](https://github.com/microsoft/plcrashreporter) diff --git a/Technotes/Logs.md b/Technotes/Logs.md index 284b29c5d..2eea4f057 100644 --- a/Technotes/Logs.md +++ b/Technotes/Logs.md @@ -1,6 +1,6 @@ # Logs -`RSCore` contains a protocol called `Logging`. Classes and Structs that conform to `Logging` have a `logger` variable that the Class or Struct can use instead of importing `os.log` and creating a `var log = Logger(..)` variable. +`RSCore` contains a protocol called `Logging`. Classes and structs that conform to `Logging` have a `logger` variable that the Class or Struct can use instead of importing `os.log` and creating a `var log = Logger(..)` variable. Example: diff --git a/Technotes/ReleaseNotes-Mac.markdown b/Technotes/ReleaseNotes-Mac.markdown index 52ecf0762..c4c935dca 100644 --- a/Technotes/ReleaseNotes-Mac.markdown +++ b/Technotes/ReleaseNotes-Mac.markdown @@ -1,5 +1,10 @@ # Mac Release Notes +### 6.1.1b2 build 6108 5 Feb 2023 + +Remove Twitter integration. On first launch, for people with Twitter feeds, display an alert explaining what happened +Fix a crashing bug that could happen in the sidebar + ### 6.1.1b1 build 6107 3 Nov 2022 Fixed a bug that could prevent users from accessing BazQux if an article was missing a field diff --git a/Technotes/ReleaseNotes-iOS.markdown b/Technotes/ReleaseNotes-iOS.markdown index fba9e6a22..9c52b0fe2 100644 --- a/Technotes/ReleaseNotes-iOS.markdown +++ b/Technotes/ReleaseNotes-iOS.markdown @@ -1,5 +1,22 @@ # iOS Release Notes +### 6.1.1 TestFlight build 6114 - 5 Feb 2023 + +Remove Twitter integration. Include alert that Twitter integration was removed. + +### 6.1.1 TestFlight build 6113 - 22 Jan 2023 + +Fix a crashing bug when fetching data for the widget + +### 6.1.1 TestFlight build 6112 - 16 Jan 2023 + +Add some feeds back to defaults — now an even 10 feeds + +### 6.1.1 TestFlight build 6111 - 8 Jan 2023 (didn’t actually go out via TestFlight) + +Fixed a crashing bug in the Feeds screen +Cut way down on number of default feeds, added BBC World News + ### 6.1 Release build 6110 - 9 Nov 2022 Changes since 6.0.1… diff --git a/iOS/Add/AddFeedViewController.swift b/iOS/Add/AddFeedViewController.swift index bd9c96373..b1cc09d70 100644 --- a/iOS/Add/AddFeedViewController.swift +++ b/iOS/Add/AddFeedViewController.swift @@ -15,7 +15,6 @@ import RSParser enum AddFeedType { case web case reddit - case twitter } class AddFeedViewController: UITableViewController { @@ -44,9 +43,6 @@ class AddFeedViewController: UITableViewController { case .reddit: navigationItem.title = NSLocalizedString("navigation.title.add-reddit-feed", comment: "Add Reddit Feed") navigationItem.leftBarButtonItem = nil - case .twitter: - navigationItem.title = NSLocalizedString("navigation.title.add-twitter-feed", comment: "Add Twitter Feed") - navigationItem.leftBarButtonItem = nil default: break } diff --git a/iOS/Add/Twitter/TwitterAdd.storyboard b/iOS/Add/Twitter/TwitterAdd.storyboard deleted file mode 100644 index f6b2db210..000000000 --- a/iOS/Add/Twitter/TwitterAdd.storyboard +++ /dev/null @@ -1,247 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/Add/Twitter/TwitterEnterDetailTableViewController.swift b/iOS/Add/Twitter/TwitterEnterDetailTableViewController.swift deleted file mode 100644 index 893b277b7..000000000 --- a/iOS/Add/Twitter/TwitterEnterDetailTableViewController.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// TwitterEnterDetailTableViewController.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 4/23/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit -import Account - -class TwitterEnterDetailTableViewController: UITableViewController { - - @IBOutlet weak var detailTextField: UITextField! - - var doneBarButtonItem = UIBarButtonItem() - var twitterFeedType: TwitterFeedType? - - override func viewDidLoad() { - super.viewDidLoad() - - doneBarButtonItem.title = NSLocalizedString("button.title.next", comment: "Next") - doneBarButtonItem.style = .plain - doneBarButtonItem.target = self - doneBarButtonItem.action = #selector(done) - navigationItem.rightBarButtonItem = doneBarButtonItem - - if case .screenName = twitterFeedType { - navigationItem.title = NSLocalizedString("navigation.title.enter-name", comment: "Enter Name") - detailTextField.placeholder = NSLocalizedString("textfield.placeholder.screen-name", comment: "Screen Name") - } else { - navigationItem.title = NSLocalizedString("navigation.title.enter-search", comment: "Enter Search") - detailTextField.placeholder = NSLocalizedString("textfield.placeholder.search-term-hashtag", comment: "Search Term or #hashtag") - } - - detailTextField.delegate = self - NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: UITextField.textDidChangeNotification, object: detailTextField) - - updateUI() - } - - @objc func done() { - guard let twitterFeedType = twitterFeedType, var text = detailTextField.text?.collapsingWhitespace else { return } - - let url: String? - if twitterFeedType == .screenName { - if text.starts(with: "@") { - text = String(text[text.index(text.startIndex, offsetBy: 1).. Bool { - textField.resignFirstResponder() - return true - } - -} - -private extension TwitterEnterDetailTableViewController { - - func updateUI() { - doneBarButtonItem.isEnabled = !(detailTextField.text?.isEmpty ?? false) - } - -} diff --git a/iOS/Add/Twitter/TwitterSelectAccountTableViewController.swift b/iOS/Add/Twitter/TwitterSelectAccountTableViewController.swift deleted file mode 100644 index 80e84be10..000000000 --- a/iOS/Add/Twitter/TwitterSelectAccountTableViewController.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// TwitterSelectAccountTableViewController.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 4/23/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit -import Account - -class TwitterSelectAccountTableViewController: UITableViewController { - - private var twitterFeedProviders = [TwitterFeedProvider]() - - var twitterFeedType: TwitterFeedType? - - override func viewDidLoad() { - super.viewDidLoad() - twitterFeedProviders = ExtensionPointManager.shared.activeExtensionPoints.values.compactMap { $0 as? TwitterFeedProvider } - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return twitterFeedProviders.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) - cell.textLabel?.text = "@\(twitterFeedProviders[indexPath.row].screenName)" - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let twitterFeedType = twitterFeedType else { return } - - let username = twitterFeedProviders[indexPath.row].screenName - let url = TwitterFeedProvider.buildURL(twitterFeedType, username: username, screenName: nil, searchField: nil)?.absoluteString - - let addViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddWebFeedViewController") as! AddFeedViewController - addViewController.addFeedType = .twitter - addViewController.initialFeed = url - navigationController?.pushViewController(addViewController, animated: true) - } - -} diff --git a/iOS/Add/Twitter/TwitterSelectTypeTableViewController.swift b/iOS/Add/Twitter/TwitterSelectTypeTableViewController.swift deleted file mode 100644 index e8bb2688b..000000000 --- a/iOS/Add/Twitter/TwitterSelectTypeTableViewController.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// TwitterSelectTypeTableViewController.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 4/23/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit -import Account - -class TwitterSelectTypeTableViewController: UITableViewController { - - private var twitterFeedProviders = [TwitterFeedProvider]() - - override func viewDidLoad() { - super.viewDidLoad() - twitterFeedProviders = ExtensionPointManager.shared.activeExtensionPoints.values.compactMap { $0 as? TwitterFeedProvider } - } - - @IBAction func cancel(_ sender: Any) { - dismiss(animated: true) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = super.tableView(tableView, cellForRowAt: indexPath) - if indexPath.row < 2 { - if twitterFeedProviders.count > 1 { - cell.accessoryType = .disclosureIndicator - } - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - switch indexPath.row { - case 0: - if twitterFeedProviders.count == 1 { - let username = twitterFeedProviders.first!.screenName - let url = TwitterFeedProvider.buildURL(.homeTimeline, username: username, screenName: nil, searchField: nil)?.absoluteString - pushAddFeedController(url) - } else { - let selectAccount = UIStoryboard.twitterAdd.instantiateController(ofType: TwitterSelectAccountTableViewController.self) - selectAccount.twitterFeedType = .homeTimeline - navigationController?.pushViewController(selectAccount, animated: true) - } - case 1: - if twitterFeedProviders.count == 1 { - let username = twitterFeedProviders.first!.screenName - let url = TwitterFeedProvider.buildURL(.mentions, username: username, screenName: nil, searchField: nil)?.absoluteString - pushAddFeedController(url) - } else { - let selectAccount = UIStoryboard.twitterAdd.instantiateController(ofType: TwitterSelectAccountTableViewController.self) - selectAccount.twitterFeedType = .mentions - navigationController?.pushViewController(selectAccount, animated: true) - } - case 2: - let enterDetail = UIStoryboard.twitterAdd.instantiateController(ofType: TwitterEnterDetailTableViewController.self) - enterDetail.twitterFeedType = .screenName - navigationController?.pushViewController(enterDetail, animated: true) - case 3: - let enterDetail = UIStoryboard.twitterAdd.instantiateController(ofType: TwitterEnterDetailTableViewController.self) - enterDetail.twitterFeedType = .search - navigationController?.pushViewController(enterDetail, animated: true) - default: - fatalError() - } - } - -} - -private extension TwitterSelectTypeTableViewController { - - func pushAddFeedController(_ url: String?) { - let addViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddWebFeedViewController") as! AddFeedViewController - addViewController.addFeedType = .twitter - addViewController.initialFeed = url - navigationController?.pushViewController(addViewController, animated: true) - } - -} diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 9c7faca3d..f4435713a 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -113,10 +113,6 @@ struct AppAssets { return UIImage(named: "contextMenuReddit")! }() - static var contextMenuTwitter: UIImage = { - return UIImage(named: "contextMenuTwitter")! - }() - static var copyImage: UIImage = { return UIImage(systemName: "doc.on.doc")! }() @@ -133,10 +129,6 @@ struct AppAssets { return RSImage(named: "extensionPointReddit")! }() - static var extensionPointTwitter: UIImage = { - return UIImage(named: "extensionPointTwitter")! - }() - static var faviconTemplateImage: RSImage = { return RSImage(named: "faviconTemplateImage")! }() @@ -280,10 +272,6 @@ struct AppAssets { return UIImage(systemName: "trash")! }() - static var twitterOriginal: UIImage = { - return UIImage(named: "twitterWhite")!.withRenderingMode(.alwaysOriginal).withTintColor(.secondaryLabel) - }() - static var unreadFeedImage: IconImage { let image = UIImage(systemName: "largecircle.fill.circle")! return IconImage(image, isSymbol: true, isBackgroundSupressed: true, preferredColor: AppAssets.secondaryAccentColor.cgColor) diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index 934df2540..6f8758715 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -30,7 +30,7 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { final class AppDefaults: ObservableObject { - static let defaultThemeName = "Default" + static let defaultThemeName = "NetNewsWire" static let shared = AppDefaults() private init() {} @@ -60,6 +60,7 @@ final class AppDefaults: ObservableObject { static let addFolderAccountID = "addFolderAccountID" static let useSystemBrowser = "useSystemBrowser" static let currentThemeName = "currentThemeName" + static let twitterDeprecationAlertShown = "twitterDeprecationAlertShown" } let isDeveloperBuild: Bool = { @@ -256,6 +257,15 @@ final class AppDefaults: ObservableObject { } } + var twitterDeprecationAlertShown: Bool { + get { + return AppDefaults.bool(for: Key.twitterDeprecationAlertShown) + } + set { + AppDefaults.setBool(for: Key.twitterDeprecationAlertShown, newValue) + } + } + static func registerDefaults() { let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue, Key.timelineGroupByFeed: false, diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index 0f1fe96ee..ac5aae104 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -267,7 +267,7 @@ class ArticleViewController: UIViewController, MainControllerIdentifiable, Loggi themeActions.append(action) } - let defaultThemeAction = UIAction(title: NSLocalizedString("button.title.default", comment: "Button title: Default"), + let defaultThemeAction = UIAction(title: "NetNewsWire", image: nil, identifier: nil, discoverabilityTitle: nil, diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 9b0d997be..3512a933d 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -94,6 +94,16 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma super.viewWillAppear(animated) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if (isBeingPresented || isMovingToParent) { + // Only show the Twitter alert the first time + // the view is presented. + presentTwitterDeprecationAlertIfRequired() + } + } + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory { @@ -617,8 +627,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma Context Menu Order: 1. Add Web Feed 2. Add Reddit Feed - 3. Add Twitter Feed - 4. Add Folder + 3. Add Folder */ var menuItems: [UIAction] = [] @@ -637,13 +646,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma } menuItems.append(addRedditFeedAction) } - if ExtensionPointManager.shared.isTwitterEnabled { - let addTwitterFeedActionTitle = NSLocalizedString("button.title.addtwitterfeed", comment: "Add Twitter Feed") - let addTwitterFeedAction = UIAction(title: addTwitterFeedActionTitle, image: AppAssets.contextMenuTwitter.tinted(color: .label)) { _ in - self.coordinator.showAddTwitterFeed() - } - menuItems.append(addTwitterFeedAction) - } } let addWebFolderActionTitle = NSLocalizedString("button.title.add-folder", comment: "Add Folder") @@ -657,7 +659,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma self.addNewItemButton.menu = contextMenu } - + func focus() { becomeFirstResponder() } @@ -670,6 +672,29 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma } } + private func presentTwitterDeprecationAlertIfRequired() { + if AppDefaults.shared.twitterDeprecationAlertShown { return } + + let expiryDate = Date(timeIntervalSince1970: 1691539200) // August 9th 2023, 00:00 UTC + let currentDate = Date() + if currentDate > expiryDate { + return // If after August 9th, don't show + } + + if AccountManager.shared.anyLocalOriCloudAccountHasAtLeastOneTwitterFeed() { + showTwitterDeprecationAlert() + } + AppDefaults.shared.twitterDeprecationAlertShown = true + } + + private func showTwitterDeprecationAlert() { + let alert = UIAlertController(title: NSLocalizedString("Twitter Integration Removed", comment: "Twitter Integration Removed"), + message: NSLocalizedString("On February 1, 2023, Twitter announced the end of free access to the Twitter API, effective February 9.\n\nSince Twitter does not provide RSS feeds, we’ve had to use the Twitter API. Without free access to that API, we can’t read feeds from Twitter.\n\nWe’ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’t delete those feeds.\n\nYou can still read whatever you have already downloaded. However, those feeds will no longer update.", comment: "Twitter deprecation message"), + preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: "OK", style: .cancel)) + present(alert, animated: true) + } } // MARK: UIContextMenuInteractionDelegate diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index fa2bbb429..fd60d7f54 100644 --- a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,116 +1,116 @@ { "images" : [ { - "size" : "20x20", + "filename" : "Icon_20x20@2x.png", "idiom" : "iphone", - "filename" : "icon-41.png", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "20x20", + "filename" : "Icon_20x20@3x.png", "idiom" : "iphone", - "filename" : "icon-60.png", - "scale" : "3x" + "scale" : "3x", + "size" : "20x20" }, { - "size" : "29x29", + "filename" : "Icon_29x29@2x.png", "idiom" : "iphone", - "filename" : "icon-58.png", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "Icon_29x29@3x.png", "idiom" : "iphone", - "filename" : "icon-87.png", - "scale" : "3x" + "scale" : "3x", + "size" : "29x29" }, { - "size" : "40x40", + "filename" : "Icon_40x40@2x.png", "idiom" : "iphone", - "filename" : "icon-80.png", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "Icon_40x40@3x.png", "idiom" : "iphone", - "filename" : "icon-121.png", - "scale" : "3x" + "scale" : "3x", + "size" : "40x40" }, { - "size" : "60x60", + "filename" : "Icon_60x60@2x.png", "idiom" : "iphone", - "filename" : "icon-120.png", - "scale" : "2x" + "scale" : "2x", + "size" : "60x60" }, { - "size" : "60x60", + "filename" : "Icon_60x60@3x.png", "idiom" : "iphone", - "filename" : "icon-180.png", - "scale" : "3x" + "scale" : "3x", + "size" : "60x60" }, { - "size" : "20x20", - "idiom" : "ipad", "filename" : "icon-20.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" }, { - "size" : "20x20", + "filename" : "Icon_20x20@2x 1.png", "idiom" : "ipad", - "filename" : "icon-42.png", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "29x29", - "idiom" : "ipad", "filename" : "icon-29.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "Icon_29x29@2x 1.png", "idiom" : "ipad", - "filename" : "icon-59.png", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "40x40", - "idiom" : "ipad", "filename" : "icon-40.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "Icon_40x40@2x 1.png", "idiom" : "ipad", - "filename" : "icon-81.png", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { - "size" : "76x76", - "idiom" : "ipad", "filename" : "icon-76.png", - "scale" : "1x" - }, - { - "size" : "76x76", "idiom" : "ipad", - "filename" : "icon-152.png", - "scale" : "2x" + "scale" : "1x", + "size" : "76x76" }, { - "size" : "83.5x83.5", + "filename" : "Icon_76x76@2x.png", "idiom" : "ipad", - "filename" : "icon-167.png", - "scale" : "2x" + "scale" : "2x", + "size" : "76x76" }, { - "size" : "1024x1024", + "filename" : "Icon_83.5x83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "Icon_1024x1024.png", "idiom" : "ios-marketing", - "filename" : "icon-1024.png", - "scale" : "1x" + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_1024x1024.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_1024x1024.png new file mode 100644 index 000000000..c23104e9b Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_1024x1024.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@2x 1.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@2x 1.png new file mode 100644 index 000000000..1dadf1062 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@2x 1.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@2x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@2x.png new file mode 100644 index 000000000..1dadf1062 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@2x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@3x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@3x.png new file mode 100644 index 000000000..05b3f4a70 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_20x20@3x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@2x 1.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@2x 1.png new file mode 100644 index 000000000..b9fc738a9 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@2x 1.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@2x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@2x.png new file mode 100644 index 000000000..b9fc738a9 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@2x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@3x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@3x.png new file mode 100644 index 000000000..79e1a4011 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_29x29@3x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@2x 1.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@2x 1.png new file mode 100644 index 000000000..44fd5c3a1 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@2x 1.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@2x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@2x.png new file mode 100644 index 000000000..44fd5c3a1 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@2x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@3x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@3x.png new file mode 100644 index 000000000..373ea7096 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_40x40@3x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_60x60@2x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_60x60@2x.png new file mode 100644 index 000000000..a137eda8b Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_60x60@2x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_60x60@3x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_60x60@3x.png new file mode 100644 index 000000000..d9a7facab Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_60x60@3x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_76x76@2x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_76x76@2x.png new file mode 100644 index 000000000..94125bb6f Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_76x76@2x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_83.5x83.5@2x.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_83.5x83.5@2x.png new file mode 100644 index 000000000..7d7cb5a2e Binary files /dev/null and b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon_83.5x83.5@2x.png differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024.png deleted file mode 100644 index c2bb5d540..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-120.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-120.png deleted file mode 100644 index 6d1a94fd9..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-120.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-121.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-121.png deleted file mode 100644 index 6d1a94fd9..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-121.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-152.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-152.png deleted file mode 100644 index b217d09c4..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-152.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-167.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-167.png deleted file mode 100644 index 4cd8fa6c0..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-167.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-180.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-180.png deleted file mode 100644 index 8c5c93b8c..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-180.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-41.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-41.png deleted file mode 100644 index 180a98b25..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-41.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-42.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-42.png deleted file mode 100644 index 180a98b25..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-42.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-58.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-58.png deleted file mode 100644 index a53d44864..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-58.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-59.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-59.png deleted file mode 100644 index a53d44864..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-59.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-60.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-60.png deleted file mode 100644 index 7a01bc978..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-60.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-80.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-80.png deleted file mode 100644 index 85289428d..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-80.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-81.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-81.png deleted file mode 100644 index 85289428d..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-81.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-87.png b/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-87.png deleted file mode 100644 index dd27b3ca3..000000000 Binary files a/iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon-87.png and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/Contents.json b/iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/Contents.json deleted file mode 100644 index 1cf455ca4..000000000 --- a/iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "twitterContextMenu.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/twitterContextMenu.pdf b/iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/twitterContextMenu.pdf deleted file mode 100644 index c7d5a5d74..000000000 Binary files a/iOS/Resources/Assets.xcassets/contextMenuTwitter.imageset/twitterContextMenu.pdf and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json b/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json deleted file mode 100644 index 46fecf23a..000000000 --- a/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "twitter.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf b/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf deleted file mode 100644 index e50de4443..000000000 Binary files a/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf and /dev/null differ diff --git a/iOS/Resources/Assets.xcassets/twitterWhite.imageset/Contents.json b/iOS/Resources/Assets.xcassets/twitterWhite.imageset/Contents.json deleted file mode 100644 index bca634d24..000000000 --- a/iOS/Resources/Assets.xcassets/twitterWhite.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "twitter_white.png", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iOS/Resources/Assets.xcassets/twitterWhite.imageset/twitter_white.png b/iOS/Resources/Assets.xcassets/twitterWhite.imageset/twitter_white.png deleted file mode 100644 index a857fb029..000000000 Binary files a/iOS/Resources/Assets.xcassets/twitterWhite.imageset/twitter_white.png and /dev/null differ diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 93aa5e5f4..52fca151b 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -1216,13 +1216,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging { masterFeedViewController.present(addNavViewController, animated: true) } - func showAddTwitterFeed() { - let addNavViewController = UIStoryboard.twitterAdd.instantiateInitialViewController() as! UINavigationController - addNavViewController.modalPresentationStyle = .formSheet - addNavViewController.preferredContentSize = AddFeedViewController.preferredContentSizeForFormSheetDisplay - masterFeedViewController.present(addNavViewController, animated: true) - } - func showAddFolder() { let addNavViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddFolderViewControllerNav") as! UINavigationController addNavViewController.modalPresentationStyle = .formSheet diff --git a/iOS/UIKit Extensions/UIStoryboard-Extensions.swift b/iOS/UIKit Extensions/UIStoryboard-Extensions.swift index a32f9b466..9d20ea374 100644 --- a/iOS/UIKit Extensions/UIStoryboard-Extensions.swift +++ b/iOS/UIKit Extensions/UIStoryboard-Extensions.swift @@ -25,10 +25,6 @@ extension UIStoryboard: Logging { return UIStoryboard(name: "RedditAdd", bundle: nil) } - static var twitterAdd: UIStoryboard { - return UIStoryboard(name: "TwitterAdd", bundle: nil) - } - static var settings: UIStoryboard { return UIStoryboard(name: "Settings", bundle: nil) } diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index 0a90fb07f..6cc3d75cc 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it -MARKETING_VERSION = 6.1 -CURRENT_PROJECT_VERSION = 6110 +MARKETING_VERSION = 6.1.1 +CURRENT_PROJECT_VERSION = 6114 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon @@ -11,3 +11,4 @@ GCC_C_LANGUAGE_STANDARD = gnu11; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = 1,2 IPHONEOS_DEPLOYMENT_TARGET = 15.0 +OTHER_LDFLAGS = $(inherited) -Xlinker -no_application_extension diff --git a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig index fc8972368..eb4b5b3a7 100644 --- a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig @@ -1,6 +1,6 @@ // High Level Settings common to both the Mac application and any extensions we bundle with it -MARKETING_VERSION = 6.1.1 -CURRENT_PROJECT_VERSION = 6107 +MARKETING_VERSION = 6.1.1b2 +CURRENT_PROJECT_VERSION = 6108 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;