Merge branch 'ios-release'
This commit is contained in:
commit
7d8e49a2ee
@ -246,6 +246,9 @@
|
|||||||
51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; };
|
51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; };
|
||||||
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; };
|
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; };
|
||||||
51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; };
|
51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; };
|
||||||
|
51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; };
|
||||||
|
51BC4B00247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; };
|
||||||
|
51BC4B01247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; };
|
||||||
51BEB22D2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */; };
|
51BEB22D2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */; };
|
||||||
51C266EA238C334800F53014 /* ContextMenuPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */; };
|
51C266EA238C334800F53014 /* ContextMenuPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */; };
|
||||||
51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; };
|
51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; };
|
||||||
@ -1552,6 +1555,7 @@
|
|||||||
51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
|
51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
|
||||||
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = "<group>"; };
|
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = "<group>"; };
|
||||||
51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = "<group>"; };
|
51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = "<group>"; };
|
||||||
|
51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL-Extensions.swift"; sourceTree = "<group>"; };
|
||||||
51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterEnterDetailTableViewController.swift; sourceTree = "<group>"; };
|
51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterEnterDetailTableViewController.swift; sourceTree = "<group>"; };
|
||||||
51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuPreviewViewController.swift; sourceTree = "<group>"; };
|
51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuPreviewViewController.swift; sourceTree = "<group>"; };
|
||||||
51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = "<group>"; };
|
51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = "<group>"; };
|
||||||
@ -2612,17 +2616,18 @@
|
|||||||
849A97561ED9EB0D007D329B /* Extensions */ = {
|
849A97561ED9EB0D007D329B /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */,
|
|
||||||
51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */,
|
51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */,
|
||||||
849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */,
|
849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */,
|
||||||
849A97581ED9EB0D007D329B /* ArticleUtilities.swift */,
|
849A97581ED9EB0D007D329B /* ArticleUtilities.swift */,
|
||||||
5108F6B52375E612001ABC45 /* CacheCleaner.swift */,
|
5108F6B52375E612001ABC45 /* CacheCleaner.swift */,
|
||||||
516AE9DE2372269A007DEEAA /* IconImage.swift */,
|
516AE9DE2372269A007DEEAA /* IconImage.swift */,
|
||||||
849A97971ED9EFAA007D329B /* Node-Extensions.swift */,
|
849A97971ED9EFAA007D329B /* Node-Extensions.swift */,
|
||||||
|
B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */,
|
||||||
8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */,
|
8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */,
|
||||||
B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */,
|
B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */,
|
||||||
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */,
|
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */,
|
||||||
84411E701FE5FBFA004B527F /* SmallIconProvider.swift */,
|
84411E701FE5FBFA004B527F /* SmallIconProvider.swift */,
|
||||||
|
51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -4157,6 +4162,7 @@
|
|||||||
65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */,
|
65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */,
|
||||||
515A5178243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */,
|
515A5178243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */,
|
||||||
65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */,
|
65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */,
|
||||||
|
51BC4B00247277E0000A6ED8 /* URL-Extensions.swift in Sources */,
|
||||||
65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */,
|
65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */,
|
||||||
65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */,
|
65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */,
|
||||||
65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */,
|
65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */,
|
||||||
@ -4478,6 +4484,7 @@
|
|||||||
51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */,
|
51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */,
|
||||||
51934CCB230F599B006127BE /* InteractiveNavigationController.swift in Sources */,
|
51934CCB230F599B006127BE /* InteractiveNavigationController.swift in Sources */,
|
||||||
769F2ED513DA03EE75B993A8 /* NewsBlurAccountViewController.swift in Sources */,
|
769F2ED513DA03EE75B993A8 /* NewsBlurAccountViewController.swift in Sources */,
|
||||||
|
51BC4B01247277E0000A6ED8 /* URL-Extensions.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -4548,6 +4555,7 @@
|
|||||||
84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */,
|
84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */,
|
||||||
51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */,
|
51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */,
|
||||||
D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */,
|
D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */,
|
||||||
|
51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */,
|
||||||
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
|
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
|
||||||
8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */,
|
8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */,
|
||||||
51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */,
|
51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */,
|
||||||
|
18
Shared/Extensions/URL-Extensions.swift
Normal file
18
Shared/Extensions/URL-Extensions.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// URL-Extensions.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Stuart Breckenridge on 03/05/2020.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
|
||||||
|
/// Extracts email address from a `URL` with a `mailto` scheme, otherwise `nil`.
|
||||||
|
var emailAddress: String? {
|
||||||
|
scheme == "mailto" ? URLComponents(url: self, resolvingAgainstBaseURL: false)?.path : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -106,11 +106,11 @@
|
|||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>title</key>
|
<key>title</key>
|
||||||
<string>Open in Browser</string>
|
<string>Open in App Browser</string>
|
||||||
<key>key</key>
|
<key>key</key>
|
||||||
<string>[return]</string>
|
<string>[return]</string>
|
||||||
<key>action</key>
|
<key>action</key>
|
||||||
<string>openInBrowser:</string>
|
<string>openFeedInAppBrowser:</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>key</key>
|
<key>key</key>
|
||||||
|
@ -47,11 +47,17 @@ private extension UserNotificationManager {
|
|||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
|
|
||||||
content.title = webFeed.nameForDisplay
|
content.title = webFeed.nameForDisplay
|
||||||
content.body = ArticleStringFormatter.truncatedTitle(article)
|
|
||||||
if content.body.isEmpty {
|
if !ArticleStringFormatter.truncatedTitle(article).isEmpty {
|
||||||
content.body = ArticleStringFormatter.truncatedSummary(article)
|
content.subtitle = ArticleStringFormatter.truncatedTitle(article)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content.body = ArticleStringFormatter.truncatedSummary(article)
|
||||||
|
|
||||||
|
content.threadIdentifier = webFeed.webFeedID
|
||||||
|
content.summaryArgument = "\(webFeed.nameForDisplay)"
|
||||||
|
content.summaryArgumentCount = 1
|
||||||
|
|
||||||
content.sound = UNNotificationSound.default
|
content.sound = UNNotificationSound.default
|
||||||
content.userInfo = [UserInfoKey.articlePath: article.pathUserInfo]
|
content.userInfo = [UserInfoKey.articlePath: article.pathUserInfo]
|
||||||
|
|
||||||
|
@ -92,10 +92,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||||||
self.unreadCount = AccountManager.shared.unreadCount
|
self.unreadCount = AccountManager.shared.unreadCount
|
||||||
}
|
}
|
||||||
|
|
||||||
UNUserNotificationCenter.current().delegate = self
|
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
|
||||||
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .sound, .alert]) { _, _ in }
|
if settings.authorizationStatus == .authorized {
|
||||||
UIApplication.shared.registerForRemoteNotifications()
|
DispatchQueue.main.async {
|
||||||
|
UIApplication.shared.registerForRemoteNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UNUserNotificationCenter.current().delegate = self
|
||||||
userNotificationManager = UserNotificationManager()
|
userNotificationManager = UserNotificationManager()
|
||||||
|
|
||||||
extensionContainersFile = ExtensionContainersFile()
|
extensionContainersFile = ExtensionContainersFile()
|
||||||
|
@ -274,7 +274,12 @@ class ArticleViewController: UIViewController {
|
|||||||
currentWebViewController?.showActivityDialog(popOverBarButtonItem: actionBarButtonItem)
|
currentWebViewController?.showActivityDialog(popOverBarButtonItem: actionBarButtonItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func toggleReaderView(_ sender: Any?) {
|
||||||
|
currentWebViewController?.toggleArticleExtractor()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Keyboard Shortcuts
|
// MARK: Keyboard Shortcuts
|
||||||
|
|
||||||
@objc func navigateToTimeline(_ sender: Any?) {
|
@objc func navigateToTimeline(_ sender: Any?) {
|
||||||
coordinator.navigateToTimeline()
|
coordinator.navigateToTimeline()
|
||||||
}
|
}
|
||||||
@ -297,6 +302,9 @@ class ArticleViewController: UIViewController {
|
|||||||
currentWebViewController?.stopArticleExtractorIfProcessing()
|
currentWebViewController?.stopArticleExtractorIfProcessing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openInAppBrowser() {
|
||||||
|
currentWebViewController?.openInAppBrowser()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Find in Article
|
// MARK: Find in Article
|
||||||
|
@ -12,6 +12,7 @@ import RSCore
|
|||||||
import Account
|
import Account
|
||||||
import Articles
|
import Articles
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
import MessageUI
|
||||||
|
|
||||||
protocol WebViewControllerDelegate: class {
|
protocol WebViewControllerDelegate: class {
|
||||||
func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState)
|
func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState)
|
||||||
@ -60,7 +61,7 @@ class WebViewController: UIViewController {
|
|||||||
|
|
||||||
private(set) var article: Article?
|
private(set) var article: Article?
|
||||||
|
|
||||||
let scrollPositionQueue = CoalescingQueue(name: "Article Scroll Position", interval: 0.3, maxInterval: 1.0)
|
let scrollPositionQueue = CoalescingQueue(name: "Article Scroll Position", interval: 0.3, maxInterval: 0.3)
|
||||||
var windowScrollY = 0
|
var windowScrollY = 0
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
@ -228,6 +229,14 @@ class WebViewController: UIViewController {
|
|||||||
present(activityViewController, animated: true)
|
present(activityViewController, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openInAppBrowser() {
|
||||||
|
guard let preferredLink = article?.preferredLink, let url = URL(string: preferredLink) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let vc = SFSafariViewController(url: url)
|
||||||
|
present(vc, animated: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ArticleExtractorDelegate
|
// MARK: ArticleExtractorDelegate
|
||||||
@ -291,7 +300,7 @@ extension WebViewController: UIContextMenuInteractionDelegate {
|
|||||||
extension WebViewController: WKNavigationDelegate {
|
extension WebViewController: WKNavigationDelegate {
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||||
if view.subviews.count > 1 {
|
while view.subviews.count > 1 {
|
||||||
view.subviews.last?.removeFromSuperview()
|
view.subviews.last?.removeFromSuperview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,6 +326,30 @@ extension WebViewController: WKNavigationDelegate {
|
|||||||
let vc = SFSafariViewController(url: url)
|
let vc = SFSafariViewController(url: url)
|
||||||
self.present(vc, animated: true)
|
self.present(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
} else if components?.scheme == "mailto" {
|
||||||
|
decisionHandler(.cancel)
|
||||||
|
|
||||||
|
guard let emailAddress = url.emailAddress else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if MFMailComposeViewController.canSendMail() {
|
||||||
|
let mailComposeViewController = MFMailComposeViewController()
|
||||||
|
mailComposeViewController.setToRecipients([emailAddress])
|
||||||
|
mailComposeViewController.mailComposeDelegate = self
|
||||||
|
self.present(mailComposeViewController, animated: true, completion: {})
|
||||||
|
} else {
|
||||||
|
let alert = UIAlertController(title: NSLocalizedString("Error", comment: "Error"), message: NSLocalizedString("This device cannot send emails.", comment: "This device cannot send emails."), preferredStyle: .alert)
|
||||||
|
alert.addAction(.init(title: NSLocalizedString("Dismiss", comment: "Dismiss"), style: .cancel, handler: nil))
|
||||||
|
self.present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
} else if components?.scheme == "tel" {
|
||||||
|
decisionHandler(.cancel)
|
||||||
|
|
||||||
|
if UIApplication.shared.canOpenURL(url) {
|
||||||
|
UIApplication.shared.open(url, options: [.universalLinksOnly : false], completionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
decisionHandler(.allow)
|
decisionHandler(.allow)
|
||||||
}
|
}
|
||||||
@ -398,6 +431,15 @@ extension WebViewController: UIScrollViewDelegate {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: MFMailComposeViewControllerDelegate
|
||||||
|
extension WebViewController: MFMailComposeViewControllerDelegate {
|
||||||
|
|
||||||
|
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||||
|
self.dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: JSON
|
// MARK: JSON
|
||||||
|
|
||||||
private struct ImageClickMessage: Codable {
|
private struct ImageClickMessage: Codable {
|
||||||
@ -687,7 +729,6 @@ private struct FindInArticleOptions: Codable {
|
|||||||
var regex = false
|
var regex = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal struct FindInArticleState: Codable {
|
internal struct FindInArticleState: Codable {
|
||||||
struct WebViewClientRect: Codable {
|
struct WebViewClientRect: Codable {
|
||||||
let x: Double
|
let x: Double
|
||||||
@ -740,4 +781,5 @@ extension WebViewController {
|
|||||||
func selectPreviousSearchResult() {
|
func selectPreviousSearchResult() {
|
||||||
webView?.evaluateJavaScript("selectPreviousResult()")
|
webView?.evaluateJavaScript("selectPreviousResult()")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Account
|
import Account
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
import UserNotifications
|
||||||
|
|
||||||
class WebFeedInspectorViewController: UITableViewController {
|
class WebFeedInspectorViewController: UITableViewController {
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ class WebFeedInspectorViewController: UITableViewController {
|
|||||||
return webFeed.homePageURL == nil
|
return webFeed.homePageURL == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var userNotificationSettings: UNNotificationSettings?
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
tableView.register(InspectorIconHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
|
tableView.register(InspectorIconHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
|
||||||
|
|
||||||
@ -51,6 +54,13 @@ class WebFeedInspectorViewController: UITableViewController {
|
|||||||
feedURLLabel.text = webFeed.url
|
feedURLLabel.text = webFeed.url
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(updateNotificationSettings), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
updateNotificationSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
@ -67,7 +77,30 @@ class WebFeedInspectorViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func notifyAboutNewArticlesChanged(_ sender: Any) {
|
@IBAction func notifyAboutNewArticlesChanged(_ sender: Any) {
|
||||||
webFeed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn
|
guard let settings = userNotificationSettings else {
|
||||||
|
notifyAboutNewArticlesSwitch.isOn = !notifyAboutNewArticlesSwitch.isOn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if settings.authorizationStatus == .denied {
|
||||||
|
notifyAboutNewArticlesSwitch.isOn = !notifyAboutNewArticlesSwitch.isOn
|
||||||
|
present(notificationUpdateErrorAlert(), animated: true, completion: nil)
|
||||||
|
} else if settings.authorizationStatus == .authorized {
|
||||||
|
webFeed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn
|
||||||
|
} else {
|
||||||
|
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .sound, .alert]) { (granted, error) in
|
||||||
|
self.updateNotificationSettings()
|
||||||
|
if granted {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.webFeed.isNotifyAboutNewArticles = self.notifyAboutNewArticlesSwitch.isOn
|
||||||
|
UIApplication.shared.registerForRemoteNotifications()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.notifyAboutNewArticlesSwitch.isOn = !self.notifyAboutNewArticlesSwitch.isOn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func alwaysShowReaderViewChanged(_ sender: Any) {
|
@IBAction func alwaysShowReaderViewChanged(_ sender: Any) {
|
||||||
@ -158,3 +191,33 @@ extension WebFeedInspectorViewController: UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: UNUserNotificationCenter
|
||||||
|
|
||||||
|
extension WebFeedInspectorViewController {
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func updateNotificationSettings() {
|
||||||
|
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.userNotificationSettings = settings
|
||||||
|
if settings.authorizationStatus == .authorized {
|
||||||
|
UIApplication.shared.registerForRemoteNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notificationUpdateErrorAlert() -> UIAlertController {
|
||||||
|
let alert = UIAlertController(title: NSLocalizedString("Enable Notifications", comment: "Notifications"),
|
||||||
|
message: NSLocalizedString("Notifications need to be enabled in the Settings app.", comment: "Notifications need to be enabled in the Settings app."), preferredStyle: .alert)
|
||||||
|
let openSettings = UIAlertAction(title: NSLocalizedString("Open Settings", comment: "Open Settings"), style: .default) { (action) in
|
||||||
|
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : false], completionHandler: nil)
|
||||||
|
}
|
||||||
|
let dismiss = UIAlertAction(title: NSLocalizedString("Dismiss", comment: "Dismiss"), style: .cancel, handler: nil)
|
||||||
|
alert.addAction(openSettings)
|
||||||
|
alert.addAction(dismiss)
|
||||||
|
return alert
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -138,6 +138,9 @@ private extension KeyboardManager {
|
|||||||
let goToStarredTitle = NSLocalizedString("Go To Starred", comment: "Go To Starred")
|
let goToStarredTitle = NSLocalizedString("Go To Starred", comment: "Go To Starred")
|
||||||
keys.append(KeyboardManager.createKeyCommand(title: goToStarredTitle, action: "goToStarred:", input: "3", modifiers: [.command]))
|
keys.append(KeyboardManager.createKeyCommand(title: goToStarredTitle, action: "goToStarred:", input: "3", modifiers: [.command]))
|
||||||
|
|
||||||
|
let gotoSettings = NSLocalizedString("Go To Settings", comment: "Go To Settings")
|
||||||
|
keys.append(KeyboardManager.createKeyCommand(title: gotoSettings, action: "goToSettings:", input: ",", modifiers: [.command]))
|
||||||
|
|
||||||
let articleSearchTitle = NSLocalizedString("Article Search", comment: "Article Search")
|
let articleSearchTitle = NSLocalizedString("Article Search", comment: "Article Search")
|
||||||
keys.append(KeyboardManager.createKeyCommand(title: articleSearchTitle, action: "articleSearch:", input: "f", modifiers: [.command, .alternate]))
|
keys.append(KeyboardManager.createKeyCommand(title: articleSearchTitle, action: "articleSearch:", input: "f", modifiers: [.command, .alternate]))
|
||||||
|
|
||||||
@ -174,6 +177,9 @@ private extension KeyboardManager {
|
|||||||
let openInBrowserTitle = NSLocalizedString("Open In Browser", comment: "Open In Browser")
|
let openInBrowserTitle = NSLocalizedString("Open In Browser", comment: "Open In Browser")
|
||||||
keys.append(KeyboardManager.createKeyCommand(title: openInBrowserTitle, action: "openInBrowser:", input: UIKeyCommand.inputRightArrow, modifiers: [.command]))
|
keys.append(KeyboardManager.createKeyCommand(title: openInBrowserTitle, action: "openInBrowser:", input: UIKeyCommand.inputRightArrow, modifiers: [.command]))
|
||||||
|
|
||||||
|
let openInAppBrowserTitle = NSLocalizedString("Open In App Browser", comment: "Open In App Browser")
|
||||||
|
keys.append(KeyboardManager.createKeyCommand(title: openInAppBrowserTitle, action: "openInAppBrowser:", input: "\r", modifiers: []))
|
||||||
|
|
||||||
let toggleReadTitle = NSLocalizedString("Toggle Read Status", comment: "Toggle Read Status")
|
let toggleReadTitle = NSLocalizedString("Toggle Read Status", comment: "Toggle Read Status")
|
||||||
keys.append(KeyboardManager.createKeyCommand(title: toggleReadTitle, action: "toggleRead:", input: "u", modifiers: [.command, .shift]))
|
keys.append(KeyboardManager.createKeyCommand(title: toggleReadTitle, action: "toggleRead:", input: "u", modifiers: [.command, .shift]))
|
||||||
|
|
||||||
@ -189,6 +195,12 @@ private extension KeyboardManager {
|
|||||||
let findInArticleTitle = NSLocalizedString("Find in Article", comment: "Find in Article")
|
let findInArticleTitle = NSLocalizedString("Find in Article", comment: "Find in Article")
|
||||||
keys.append(KeyboardManager.createKeyCommand(title: findInArticleTitle, action: "beginFind:", input: "f", modifiers: [.command]))
|
keys.append(KeyboardManager.createKeyCommand(title: findInArticleTitle, action: "beginFind:", input: "f", modifiers: [.command]))
|
||||||
|
|
||||||
|
let toggleSidebar = NSLocalizedString("Toggle Sidebar", comment: "Toggle Sidebar")
|
||||||
|
keys.append(KeyboardManager.createKeyCommand(title: toggleSidebar, action: "toggleSidebar:", input: "s", modifiers: [.command, .control]))
|
||||||
|
|
||||||
|
let toggleReaderView = NSLocalizedString("Toggle Reader View", comment: "Toggle Reader View")
|
||||||
|
keys.append(KeyboardManager.createKeyCommand(title: toggleReaderView, action: "toggleReaderView:", input: "r", modifiers: [.command, .shift]))
|
||||||
|
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import Account
|
|||||||
import Articles
|
import Articles
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSTree
|
import RSTree
|
||||||
|
import SafariServices
|
||||||
|
|
||||||
class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||||
|
|
||||||
@ -459,6 +460,17 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func markAllAsRead(_ sender: Any) {
|
||||||
|
guard let indexPath = tableView.indexPathForSelectedRow, let contentView = tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
|
||||||
|
MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||||
|
self?.coordinator.markAllAsReadInTimeline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: API
|
// MARK: API
|
||||||
|
|
||||||
func restoreSelectionIfNecessary(adjustScroll: Bool) {
|
func restoreSelectionIfNecessary(adjustScroll: Bool) {
|
||||||
@ -504,6 +516,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||||||
becomeFirstResponder()
|
becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openInAppBrowser() {
|
||||||
|
if let indexPath = coordinator.currentFeedIndexPath,
|
||||||
|
let url = coordinator.homePageURLForFeed(indexPath) {
|
||||||
|
let vc = SFSafariViewController(url: url)
|
||||||
|
present(vc, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: UIContextMenuInteractionDelegate
|
// MARK: UIContextMenuInteractionDelegate
|
||||||
@ -937,7 +956,7 @@ private extension MasterFeedViewController {
|
|||||||
guard let node = dataSource.itemIdentifier(for: indexPath),
|
guard let node = dataSource.itemIdentifier(for: indexPath),
|
||||||
coordinator.unreadCountFor(node) > 0,
|
coordinator.unreadCountFor(node) > 0,
|
||||||
let feed = node.representedObject as? WebFeed,
|
let feed = node.representedObject as? WebFeed,
|
||||||
let articles = try? feed.fetchArticles() else {
|
let articles = try? feed.fetchArticles(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -947,8 +966,9 @@ private extension MasterFeedViewController {
|
|||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, cancelCompletion: cancel) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
|
||||||
self?.coordinator.markAllAsRead(Array(articles))
|
self?.coordinator.markAllAsRead(Array(articles))
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
@ -1026,7 +1046,7 @@ private extension MasterFeedViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let articles = Array(fetchedArticles)
|
let articles = Array(fetchedArticles)
|
||||||
return markAllAsReadAction(articles: articles, nameForDisplay: articleFetcher.nameForDisplay)
|
return markAllAsReadAction(articles: articles, nameForDisplay: articleFetcher.nameForDisplay, indexPath: indexPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAllAsReadAction(account: Account) -> UIAction? {
|
func markAllAsReadAction(account: Account) -> UIAction? {
|
||||||
@ -1038,8 +1058,8 @@ private extension MasterFeedViewController {
|
|||||||
return markAllAsReadAction(articles: articles, nameForDisplay: account.nameForDisplay)
|
return markAllAsReadAction(articles: articles, nameForDisplay: account.nameForDisplay)
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAllAsReadAction(articles: [Article], nameForDisplay: String) -> UIAction? {
|
func markAllAsReadAction(articles: [Article], nameForDisplay: String, indexPath: IndexPath? = nil) -> UIAction? {
|
||||||
guard articles.canMarkAllAsRead() else {
|
guard articles.canMarkAllAsRead(), let indexPath = indexPath, let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1047,7 +1067,7 @@ private extension MasterFeedViewController {
|
|||||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, nameForDisplay) as String
|
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, nameForDisplay) as String
|
||||||
|
|
||||||
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
|
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||||
self?.coordinator.markAllAsRead(articles)
|
self?.coordinator.markAllAsRead(articles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,20 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
protocol MarkAsReadAlertControllerSourceType {}
|
||||||
|
extension CGRect: MarkAsReadAlertControllerSourceType {}
|
||||||
|
extension UIView: MarkAsReadAlertControllerSourceType {}
|
||||||
|
extension UIBarButtonItem: MarkAsReadAlertControllerSourceType {}
|
||||||
|
|
||||||
|
|
||||||
struct MarkAsReadAlertController {
|
struct MarkAsReadAlertController {
|
||||||
|
|
||||||
static func confirm(_ controller: UIViewController?,
|
static func confirm<T>(_ controller: UIViewController?,
|
||||||
coordinator: SceneCoordinator?,
|
coordinator: SceneCoordinator?,
|
||||||
confirmTitle: String,
|
confirmTitle: String,
|
||||||
cancelCompletion: (() -> Void)? = nil,
|
sourceType: T,
|
||||||
completion: @escaping () -> Void) {
|
cancelCompletion: (() -> Void)? = nil,
|
||||||
|
completion: @escaping () -> Void) where T: MarkAsReadAlertControllerSourceType {
|
||||||
|
|
||||||
guard let controller = controller, let coordinator = coordinator else {
|
guard let controller = controller, let coordinator = coordinator else {
|
||||||
completion()
|
completion()
|
||||||
@ -23,7 +30,7 @@ struct MarkAsReadAlertController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if AppDefaults.confirmMarkAllAsRead {
|
if AppDefaults.confirmMarkAllAsRead {
|
||||||
let alertController = MarkAsReadAlertController.alert(coordinator: coordinator, confirmTitle: confirmTitle, cancelCompletion: cancelCompletion) { _ in
|
let alertController = MarkAsReadAlertController.alert(coordinator: coordinator, confirmTitle: confirmTitle, cancelCompletion: cancelCompletion, sourceType: sourceType) { _ in
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
controller.present(alertController, animated: true)
|
controller.present(alertController, animated: true)
|
||||||
@ -32,10 +39,12 @@ struct MarkAsReadAlertController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func alert(coordinator: SceneCoordinator,
|
private static func alert<T>(coordinator: SceneCoordinator,
|
||||||
confirmTitle: String,
|
confirmTitle: String,
|
||||||
cancelCompletion: (() -> Void)?,
|
cancelCompletion: (() -> Void)?,
|
||||||
completion: @escaping (UIAlertAction) -> Void) -> UIAlertController {
|
sourceType: T,
|
||||||
|
completion: @escaping (UIAlertAction) -> Void) -> UIAlertController where T: MarkAsReadAlertControllerSourceType {
|
||||||
|
|
||||||
|
|
||||||
let title = NSLocalizedString("Mark As Read", comment: "Mark As Read")
|
let title = NSLocalizedString("Mark As Read", comment: "Mark As Read")
|
||||||
let message = NSLocalizedString("You can turn this confirmation off in settings.",
|
let message = NSLocalizedString("You can turn this confirmation off in settings.",
|
||||||
@ -43,7 +52,7 @@ struct MarkAsReadAlertController {
|
|||||||
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
|
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
|
||||||
let settingsTitle = NSLocalizedString("Open Settings", comment: "Open Settings")
|
let settingsTitle = NSLocalizedString("Open Settings", comment: "Open Settings")
|
||||||
|
|
||||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
|
||||||
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) { _ in
|
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) { _ in
|
||||||
cancelCompletion?()
|
cancelCompletion?()
|
||||||
}
|
}
|
||||||
@ -56,6 +65,18 @@ struct MarkAsReadAlertController {
|
|||||||
alertController.addAction(settingsAction)
|
alertController.addAction(settingsAction)
|
||||||
alertController.addAction(cancelAction)
|
alertController.addAction(cancelAction)
|
||||||
|
|
||||||
|
if let barButtonItem = sourceType as? UIBarButtonItem {
|
||||||
|
alertController.popoverPresentationController?.barButtonItem = barButtonItem
|
||||||
|
}
|
||||||
|
|
||||||
|
if let rect = sourceType as? CGRect {
|
||||||
|
alertController.popoverPresentationController?.sourceRect = rect
|
||||||
|
}
|
||||||
|
|
||||||
|
if let view = sourceType as? UIView {
|
||||||
|
alertController.popoverPresentationController?.sourceView = view
|
||||||
|
}
|
||||||
|
|
||||||
return alertController
|
return alertController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,8 +116,21 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||||||
|
|
||||||
@IBAction func markAllAsRead(_ sender: Any) {
|
@IBAction func markAllAsRead(_ sender: Any) {
|
||||||
let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
|
let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title) { [weak self] in
|
|
||||||
self?.coordinator.markAllAsReadInTimeline()
|
if let source = sender as? UIBarButtonItem {
|
||||||
|
MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title, sourceType: source) { [weak self] in
|
||||||
|
self?.coordinator.markAllAsReadInTimeline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = sender as? UIKeyCommand {
|
||||||
|
guard let indexPath = tableView.indexPathForSelectedRow, let contentView = tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||||
|
self?.coordinator.markAllAsReadInTimeline()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,11 +273,11 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||||||
popoverController.sourceRect = CGRect(x: view.frame.size.width/2, y: view.frame.size.height/2, width: 1, height: 1)
|
popoverController.sourceRect = CGRect(x: view.frame.size.width/2, y: view.frame.size.height/2, width: 1, height: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let action = self.markAboveAsReadAlertAction(article, completion: completion) {
|
if let action = self.markAboveAsReadAlertAction(article, indexPath: indexPath, completion: completion) {
|
||||||
alert.addAction(action)
|
alert.addAction(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let action = self.markBelowAsReadAlertAction(article, completion: completion) {
|
if let action = self.markBelowAsReadAlertAction(article, indexPath: indexPath, completion: completion) {
|
||||||
alert.addAction(action)
|
alert.addAction(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +285,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||||||
alert.addAction(action)
|
alert.addAction(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let action = self.markAllInFeedAsReadAlertAction(article, completion: completion) {
|
if let action = self.markAllInFeedAsReadAlertAction(article, indexPath: indexPath, completion: completion) {
|
||||||
alert.addAction(action)
|
alert.addAction(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,11 +330,11 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||||||
|
|
||||||
actions.append(self.toggleArticleStarStatusAction(article))
|
actions.append(self.toggleArticleStarStatusAction(article))
|
||||||
|
|
||||||
if let action = self.markAboveAsReadAction(article) {
|
if let action = self.markAboveAsReadAction(article, indexPath: indexPath) {
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let action = self.markBelowAsReadAction(article) {
|
if let action = self.markBelowAsReadAction(article, indexPath: indexPath) {
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,7 +342,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||||||
actions.append(action)
|
actions.append(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let action = self.markAllInFeedAsReadAction(article) {
|
if let action = self.markAllInFeedAsReadAction(article, indexPath: indexPath) {
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,38 +709,38 @@ private extension MasterTimelineViewController {
|
|||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAboveAsReadAction(_ article: Article) -> UIAction? {
|
func markAboveAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
|
||||||
guard coordinator.canMarkAboveAsRead(for: article) else {
|
guard coordinator.canMarkAboveAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = NSLocalizedString("Mark Above as Read", comment: "Mark Above as Read")
|
let title = NSLocalizedString("Mark Above as Read", comment: "Mark Above as Read")
|
||||||
let image = AppAssets.markAboveAsReadImage
|
let image = AppAssets.markAboveAsReadImage
|
||||||
let action = UIAction(title: title, image: image) { [weak self] action in
|
let action = UIAction(title: title, image: image) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||||
self?.coordinator.markAboveAsRead(article)
|
self?.coordinator.markAboveAsRead(article)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
|
|
||||||
func markBelowAsReadAction(_ article: Article) -> UIAction? {
|
func markBelowAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
|
||||||
guard coordinator.canMarkBelowAsRead(for: article) else {
|
guard coordinator.canMarkBelowAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = NSLocalizedString("Mark Below as Read", comment: "Mark Below as Read")
|
let title = NSLocalizedString("Mark Below as Read", comment: "Mark Below as Read")
|
||||||
let image = AppAssets.markBelowAsReadImage
|
let image = AppAssets.markBelowAsReadImage
|
||||||
let action = UIAction(title: title, image: image) { [weak self] action in
|
let action = UIAction(title: title, image: image) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||||
self?.coordinator.markBelowAsRead(article)
|
self?.coordinator.markBelowAsRead(article)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAboveAsReadAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
func markAboveAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||||
guard coordinator.canMarkAboveAsRead(for: article) else {
|
guard coordinator.canMarkAboveAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -737,7 +750,7 @@ private extension MasterTimelineViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, cancelCompletion: cancel) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
|
||||||
self?.coordinator.markAboveAsRead(article)
|
self?.coordinator.markAboveAsRead(article)
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
@ -745,8 +758,8 @@ private extension MasterTimelineViewController {
|
|||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
|
|
||||||
func markBelowAsReadAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
func markBelowAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||||
guard coordinator.canMarkBelowAsRead(for: article) else {
|
guard coordinator.canMarkBelowAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -756,7 +769,7 @@ private extension MasterTimelineViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, cancelCompletion: cancel) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
|
||||||
self?.coordinator.markBelowAsRead(article)
|
self?.coordinator.markBelowAsRead(article)
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
@ -787,36 +800,37 @@ private extension MasterTimelineViewController {
|
|||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAllInFeedAsReadAction(_ article: Article) -> UIAction? {
|
func markAllInFeedAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
|
||||||
guard let webFeed = article.webFeed else { return nil }
|
guard let webFeed = article.webFeed else { return nil }
|
||||||
guard let fetchedArticles = try? webFeed.fetchArticles() else {
|
guard let fetchedArticles = try? webFeed.fetchArticles() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let articles = Array(fetchedArticles)
|
let articles = Array(fetchedArticles)
|
||||||
guard articles.canMarkAllAsRead() else {
|
guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
|
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
|
||||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
|
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
|
||||||
|
|
||||||
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
|
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||||
self?.coordinator.markAllAsRead(articles)
|
self?.coordinator.markAllAsRead(articles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAllInFeedAsReadAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
func markAllInFeedAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||||
guard let webFeed = article.webFeed else { return nil }
|
guard let webFeed = article.webFeed else { return nil }
|
||||||
guard let fetchedArticles = try? webFeed.fetchArticles() else {
|
guard let fetchedArticles = try? webFeed.fetchArticles() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let articles = Array(fetchedArticles)
|
let articles = Array(fetchedArticles)
|
||||||
guard articles.canMarkAllAsRead() else {
|
guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -827,7 +841,7 @@ private extension MasterTimelineViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, cancelCompletion: cancel) { [weak self] in
|
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
|
||||||
self?.coordinator.markAllAsRead(articles)
|
self?.coordinator.markAllAsRead(articles)
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,14 @@ class RootSplitViewController: UISplitViewController {
|
|||||||
coordinator.showBrowserForCurrentArticle()
|
coordinator.showBrowserForCurrentArticle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func openInAppBrowser(_ sender: Any?) {
|
||||||
|
coordinator.showInAppBrowserForCurrentArticle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func openFeedInAppBrowser(_ sender: Any?) {
|
||||||
|
coordinator.showInAppBrowserForCurrentFeed()
|
||||||
|
}
|
||||||
|
|
||||||
@objc func articleSearch(_ sender: Any?) {
|
@objc func articleSearch(_ sender: Any?) {
|
||||||
coordinator.showSearch()
|
coordinator.showSearch()
|
||||||
}
|
}
|
||||||
@ -122,6 +130,10 @@ class RootSplitViewController: UISplitViewController {
|
|||||||
coordinator.selectStarredFeed()
|
coordinator.selectStarredFeed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func goToSettings(_ sender: Any?) {
|
||||||
|
coordinator.showSettings()
|
||||||
|
}
|
||||||
|
|
||||||
@objc func toggleRead(_ sender: Any?) {
|
@objc func toggleRead(_ sender: Any?) {
|
||||||
coordinator.toggleReadForCurrentArticle()
|
coordinator.toggleReadForCurrentArticle()
|
||||||
}
|
}
|
||||||
@ -130,4 +142,7 @@ class RootSplitViewController: UISplitViewController {
|
|||||||
coordinator.toggleStarredForCurrentArticle()
|
coordinator.toggleStarredForCurrentArticle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func toggleSidebar(_ sender: Any?) {
|
||||||
|
coordinator.toggleSidebar()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import Account
|
|||||||
import Articles
|
import Articles
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSTree
|
import RSTree
|
||||||
|
import SafariServices
|
||||||
|
|
||||||
enum PanelMode {
|
enum PanelMode {
|
||||||
case unset
|
case unset
|
||||||
@ -750,7 +751,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||||||
|
|
||||||
setTimelineFeed(nil, animated: false) {
|
setTimelineFeed(nil, animated: false) {
|
||||||
if self.isReadFeedsFiltered {
|
if self.isReadFeedsFiltered {
|
||||||
self.queueRebuildBackingStores()
|
self.rebuildBackingStores()
|
||||||
}
|
}
|
||||||
self.activityManager.invalidateSelecting()
|
self.activityManager.invalidateSelecting()
|
||||||
if self.rootSplitViewController.isCollapsed && self.navControllerForTimeline().viewControllers.last is MasterTimelineViewController {
|
if self.rootSplitViewController.isCollapsed && self.navControllerForTimeline().viewControllers.last is MasterTimelineViewController {
|
||||||
@ -1185,6 +1186,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||||||
UIApplication.shared.open(url, options: [:])
|
UIApplication.shared.open(url, options: [:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showInAppBrowserForCurrentArticle() {
|
||||||
|
articleViewController?.openInAppBrowser()
|
||||||
|
}
|
||||||
|
|
||||||
|
func showInAppBrowserForCurrentFeed() {
|
||||||
|
masterFeedViewController.openInAppBrowser()
|
||||||
|
}
|
||||||
|
|
||||||
func navigateToFeeds() {
|
func navigateToFeeds() {
|
||||||
masterFeedViewController?.focus()
|
masterFeedViewController?.focus()
|
||||||
selectArticle(nil)
|
selectArticle(nil)
|
||||||
@ -1201,6 +1210,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||||||
articleViewController?.focus()
|
articleViewController?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toggleSidebar() {
|
||||||
|
rootSplitViewController.preferredDisplayMode = rootSplitViewController.displayMode == .allVisible ? .primaryHidden : .allVisible
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: UISplitViewControllerDelegate
|
// MARK: UISplitViewControllerDelegate
|
||||||
|
@ -34,6 +34,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
|
<key>NSExtensionActivationSupportsText</key>
|
||||||
|
<string>1</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSExtensionJavaScriptPreprocessingFile</key>
|
<key>NSExtensionJavaScriptPreprocessingFile</key>
|
||||||
<string>SafariExt</string>
|
<string>SafariExt</string>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
// High Level Settings common to both the iOS application and any extensions we bundle with it
|
// High Level Settings common to both the iOS application and any extensions we bundle with it
|
||||||
MARKETING_VERSION = 5.0.1
|
MARKETING_VERSION = 5.0.1
|
||||||
CURRENT_PROJECT_VERSION = 45
|
CURRENT_PROJECT_VERSION = 46
|
||||||
|
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||||
|
Loading…
x
Reference in New Issue
Block a user