diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index 13244e996..cefbe0423 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -50,7 +50,7 @@ struct AppAssets { static let nnwFeedIcon = RSImage(named: "nnwFeedIcon")! - @available(macOS 11.0, *) + static var addNewSidebarItemImage: RSImage = { return NSImage(systemSymbolName: "plus", accessibilityDescription: nil)! }() @@ -67,14 +67,22 @@ struct AppAssets { return RSImage(named: "articleExtractorOn")! }() - @available(macOS 11.0, *) + static var articleTheme: RSImage = { return NSImage(systemSymbolName: "doc.richtext", accessibilityDescription: nil)! }() - @available(macOS 11.0, *) + static var cleanUpImage: RSImage = { - return NSImage(systemSymbolName: "wind", accessibilityDescription: nil)! + return NSImage(systemSymbolName: "bubbles.and.sparkles", accessibilityDescription: nil)! + }() + + static var copyImage: RSImage = { + return NSImage(systemSymbolName: "document.on.document", accessibilityDescription: nil)! + }() + + static var deleteImage: RSImage = { + return NSImage(systemSymbolName: "xmark.bin", accessibilityDescription: nil)! }() static var marsEditIcon: RSImage = { @@ -90,19 +98,11 @@ struct AppAssets { }() static var filterActive: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "line.horizontal.3.decrease.circle.fill", accessibilityDescription: nil)! - } else { - return RSImage(named: "filterActive")! - } + return NSImage(systemSymbolName: "line.horizontal.3.decrease.circle.fill", accessibilityDescription: nil)! }() static var filterInactive: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "line.horizontal.3.decrease.circle", accessibilityDescription: nil)! - } else { - return RSImage(named: "filterInactive")! - } + return NSImage(systemSymbolName: "line.horizontal.3.decrease.circle", accessibilityDescription: nil)! }() static var iconLightBackgroundColor: NSColor = { @@ -146,100 +146,94 @@ struct AppAssets { }() static var mainFolderImage: IconImage { - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! - let preferredColor = NSColor(named: "AccentColor")! - return IconImage(image, isSymbol: true, isBackgroundSuppressed: true, preferredColor: preferredColor.cgColor) - } else { - return IconImage(RSImage(named: NSImage.folderName)!) - } + let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! + let preferredColor = NSColor(named: "AccentColor")! + return IconImage(image, isSymbol: true, isBackgroundSuppressed: true, preferredColor: preferredColor.cgColor) } + + static var markAllAsReadMenuImage: RSImage = { + let image = RSImage(named: "markAllAsRead") + return image! + }() + static var markAllAsReadImage: RSImage = { - return RSImage(named: "markAllAsRead")! + let image = RSImage(named: "markAllAsRead") + return image! }() - @available(macOS 11.0, *) + static var nextUnreadImage: RSImage = { return NSImage(systemSymbolName: "chevron.down.circle", accessibilityDescription: nil)! }() - @available(macOS 11.0, *) + static var notificationImage: RSImage = { + return NSImage(systemSymbolName: "bell.badge", accessibilityDescription: nil)! + }() + static var openInBrowserImage: RSImage = { return NSImage(systemSymbolName: "safari", accessibilityDescription: nil)! }() static var preferencesToolbarAccountsImage: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "at", accessibilityDescription: nil)! - } else { - return NSImage(named: NSImage.userAccountsName)! - } + return NSImage(systemSymbolName: "at", accessibilityDescription: nil)! }() static var preferencesToolbarGeneralImage: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil)! - } else { - return NSImage(named: NSImage.preferencesGeneralName)! - } + return NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil)! }() static var preferencesToolbarAdvancedImage: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: nil)! - } else { - return NSImage(named: NSImage.advancedName)! - } + return NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: nil)! }() - @available(macOS 11.0, *) + static var readClosedImage: RSImage = { return NSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: nil)! }() - @available(macOS 11.0, *) + static var readOpenImage: RSImage = { return NSImage(systemSymbolName: "circle", accessibilityDescription: nil)! }() - @available(macOS 11.0, *) + static var refreshImage: RSImage = { return NSImage(systemSymbolName: "arrow.clockwise", accessibilityDescription: nil)! }() + static var renameImage: RSImage = { + return NSImage(systemSymbolName: "pencil", accessibilityDescription: nil)! + }() + static var searchFeedImage: IconImage = { return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!, isSymbol: true, isBackgroundSuppressed: true) }() - @available(macOS 11.0, *) + static var shareImage: RSImage = { return NSImage(systemSymbolName: "square.and.arrow.up", accessibilityDescription: nil)! }() - @available(macOS 11.0, *) + static var sidebarToggleImage: RSImage = { return NSImage(systemSymbolName: "sidebar.left", accessibilityDescription: nil)! }() - @available(macOS 11.0, *) + static var starClosedImage: RSImage = { return NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)! }() - @available(macOS 11.0, *) + static var starOpenImage: RSImage = { return NSImage(systemSymbolName: "star", accessibilityDescription: nil)! }() static var starredFeedImage: IconImage = { - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)! - let preferredColor = NSColor(named: "StarColor")! - return IconImage(image, isSymbol: true, isBackgroundSuppressed: true, preferredColor: preferredColor.cgColor) - } else { - return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!, isBackgroundSuppressed: true) - } + let image = NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)! + let preferredColor = NSColor(named: "StarColor")! + return IconImage(image, isSymbol: true, isBackgroundSuppressed: true, preferredColor: preferredColor.cgColor) }() static var timelineSeparatorColor: NSColor = { @@ -255,61 +249,31 @@ struct AppAssets { }() static var todayFeedImage: IconImage = { - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)! - let preferredColor = NSColor.orange - return IconImage(image, isSymbol: true, isBackgroundSuppressed: true, preferredColor: preferredColor.cgColor) - } else { - return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!, isBackgroundSuppressed: true) - } + let image = NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)! + let preferredColor = NSColor.orange + return IconImage(image, isSymbol: true, isBackgroundSuppressed: true, preferredColor: preferredColor.cgColor) }() static var unreadFeedImage: IconImage = { - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: nil)! - let preferredColor = NSColor(named: "AccentColor")! - return IconImage(image, isSymbol: true, isBackgroundSuppressed: true, preferredColor: preferredColor.cgColor) - } else { - return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!, isBackgroundSuppressed: true) - } + let image = NSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: nil)! + let preferredColor = NSColor(named: "AccentColor")! + return IconImage(image, isSymbol: true, isBackgroundSuppressed: true, preferredColor: preferredColor.cgColor) }() static var swipeMarkReadImage: RSImage = { - if #available(OSX 11.0, *) { - return RSImage(systemSymbolName: "circle", accessibilityDescription: "Mark Read")! - .withSymbolConfiguration(.init(scale: .large))! - } else { - // TODO: remove swipeMarkRead asset when dropping support for macOS 10.15 - return RSImage(named: "swipeMarkRead")! - } + return RSImage(systemSymbolName: "circle", accessibilityDescription: "Mark Read")! }() static var swipeMarkUnreadImage: RSImage = { - if #available(OSX 11.0, *) { - return RSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: "Mark Unread")! - .withSymbolConfiguration(.init(scale: .large))! - } else { - // TODO: remove swipeMarkUnread asset when dropping support for macOS 10.15 - return RSImage(named: "swipeMarkUnread")! - } + return RSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: "Mark Unread")! }() static var swipeMarkStarredImage: RSImage = { - if #available(OSX 11.0, *) { - return RSImage(systemSymbolName: "star.fill", accessibilityDescription: "Star")! - .withSymbolConfiguration(.init(scale: .large))! - } else { - return RSImage(named: "swipeMarkStarred")! - } + return RSImage(systemSymbolName: "star.fill", accessibilityDescription: "Star")! }() static var swipeMarkUnstarredImage: RSImage = { - if #available(OSX 11.0, *) { - return RSImage(systemSymbolName: "star", accessibilityDescription: "Unstar")! - .withSymbolConfiguration(.init(scale: .large))! - } else { - return RSImage(named: "swipeMarkUnstarred")! - } + return RSImage(systemSymbolName: "star", accessibilityDescription: "Unstar")! }() static var starColor: NSColor = { diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 79a53d955..adf91c8a5 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -798,30 +798,20 @@ internal extension AppDelegate { attrs[.font] = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) attrs[.foregroundColor] = NSColor.textColor - if #available(macOS 11.0, *) { - let titleParagraphStyle = NSMutableParagraphStyle() - titleParagraphStyle.alignment = .center - attrs[.paragraphStyle] = titleParagraphStyle - } - + let titleParagraphStyle = NSMutableParagraphStyle() + titleParagraphStyle.alignment = .center + attrs[.paragraphStyle] = titleParagraphStyle + let websiteText = NSMutableAttributedString() websiteText.append(NSAttributedString(string: NSLocalizedString("Author‘s website:", comment: "Author's Website"), attributes: attrs)) - if #available(macOS 11.0, *) { - websiteText.append(NSAttributedString(string: "\n")) - } else { - websiteText.append(NSAttributedString(string: " ")) - } + websiteText.append(NSAttributedString(string: "\n")) attrs[.link] = theme.creatorHomePage websiteText.append(NSAttributedString(string: theme.creatorHomePage, attributes: attrs)) let textViewWidth: CGFloat - if #available(macOS 11.0, *) { - textViewWidth = 200 - } else { - textViewWidth = 400 - } + textViewWidth = 200 let textView = NSTextView(frame: CGRect(x: 0, y: 0, width: textViewWidth, height: 15)) textView.isEditable = false diff --git a/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift b/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift index bbd1a928d..3c6c7b908 100644 --- a/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift +++ b/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift @@ -64,8 +64,6 @@ private extension BuiltinSmartFeedInspectorViewController { func updateUI() { nameTextField?.stringValue = smartFeed?.nameForDisplay ?? "" windowTitle = smartFeed?.nameForDisplay ?? NSLocalizedString("Smart Feed Inspector", comment: "Smart Feed Inspector window title") - if #available(macOS 11.0, *) { - smartFeedImageView?.image = smartFeed?.smallIcon?.image - } + smartFeedImageView?.image = smartFeed?.smallIcon?.image } } diff --git a/Mac/Inspector/FolderInspectorViewController.swift b/Mac/Inspector/FolderInspectorViewController.swift index d67dfca80..63f76b0d7 100644 --- a/Mac/Inspector/FolderInspectorViewController.swift +++ b/Mac/Inspector/FolderInspectorViewController.swift @@ -47,11 +47,9 @@ final class FolderInspectorViewController: NSViewController, Inspector { override func viewDidLoad() { updateUI() - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! - folderImageView.image = image - folderImageView.contentTintColor = NSColor.controlAccentColor - } + let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! + folderImageView.image = image + folderImageView.contentTintColor = NSColor.controlAccentColor NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) } diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 2f82fed78..b026fbc7d 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -120,19 +120,6 @@ final class DetailWebViewController: NSViewController { view = webView - // Use the safe area layout guides if they are available. - if #available(OSX 11.0, *) { - // These constraints have been removed as they were unsatisfiable after removing NSBox. - } else { - let constraints = [ - webView.topAnchor.constraint(equalTo: view.topAnchor), - webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - ] - NSLayoutConstraint.activate(constraints) - } - // Hide the web view until the first reload (navigation) is complete (plus some delay) to avoid the awful white flash that happens on the initial display in dark mode. // See bug #901. webView.isHidden = true @@ -346,9 +333,6 @@ private extension DetailWebViewController { func fetchScrollInfo(_ completion: @escaping (ScrollInfo?) -> Void) { var javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x" - if #available(macOS 10.15, *) { - javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: window.pageYOffset}; x" - } webView.evaluateJavaScript(javascriptString) { (info, error) in guard let info = info as? [String: Any] else { diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index a0b9932c5..0f90b85b1 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -854,11 +854,11 @@ extension MainWindowController: NSToolbarDelegate { func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { [ + NSToolbarItem.Identifier.toggleSidebar, .flexibleSpace, .refresh, .newSidebarItemMenu, .sidebarTrackingSeparator, - NSToolbarItem.Identifier.toggleSidebar, .markAllAsRead, .toggleReadArticlesFilter, .timelineTrackingSeparator, diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift index d3154e2c8..190e48321 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift @@ -78,6 +78,7 @@ class SidebarCell : NSTableCellView { override var backgroundStyle: NSView.BackgroundStyle { didSet { updateFaviconImage() + unreadCountView.isSelected = (backgroundStyle != .normal) } } @@ -164,4 +165,3 @@ private extension SidebarCell { } } - diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift index 7a082b434..c13996732 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift @@ -23,11 +23,7 @@ struct SidebarCellAppearance: Equatable { textFieldFontSize = 11 case .large: imageSize = CGSize(width: 22, height: 22) - if #available(macOS 11.0, *) { - textFieldFontSize = 15 - } else { - textFieldFontSize = 13 - } + textFieldFontSize = 15 default: imageSize = CGSize(width: 19, height: 19) textFieldFontSize = 13 diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index 3944c1bfd..4bff3aca6 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -216,23 +216,23 @@ private extension SidebarViewController { } if let homePageURL = webFeed.homePageURL, let _ = URL(string: homePageURL) { - let item = menuItem(NSLocalizedString("Open Home Page", comment: "Command"), #selector(openHomePageFromContextualMenu(_:)), homePageURL) + let item = menuItem(NSLocalizedString("Open Home Page", comment: "Command"), #selector(openHomePageFromContextualMenu(_:)), homePageURL, image: AppAssets.openInBrowserImage) menu.addItem(item) menu.addItem(NSMenuItem.separator()) } - let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), webFeed.url) + let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), webFeed.url, image: AppAssets.copyImage) menu.addItem(copyFeedURLItem) if let homePageURL = webFeed.homePageURL { - let item = menuItem(NSLocalizedString("Copy Home Page URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), homePageURL) + let item = menuItem(NSLocalizedString("Copy Home Page URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), homePageURL, image: AppAssets.copyImage) menu.addItem(item) } menu.addItem(NSMenuItem.separator()) let notificationText = webFeed.notificationDisplayName.capitalized - let notificationMenuItem = menuItem(notificationText, #selector(toggleNotificationsFromContextMenu(_:)), webFeed) + let notificationMenuItem = menuItem(notificationText, #selector(toggleNotificationsFromContextMenu(_:)), webFeed, image: AppAssets.notificationImage) if webFeed.isNotifyAboutNewArticles == nil || webFeed.isNotifyAboutNewArticles! == false { notificationMenuItem.state = .off } else { @@ -241,7 +241,7 @@ private extension SidebarViewController { menu.addItem(notificationMenuItem) let articleExtractorText = NSLocalizedString("Always Use Reader View", comment: "Always Use Reader View") - let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), webFeed) + let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), webFeed, image: AppAssets.articleExtractorOff) if webFeed.isArticleExtractorAlwaysOn == nil || webFeed.isArticleExtractorAlwaysOn! == false { articleExtractorMenuItem.state = .off @@ -301,17 +301,17 @@ private extension SidebarViewController { func markAllReadMenuItem(_ objects: [Any]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark All as Read", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects) + return menuItem(NSLocalizedString("Mark All as Read", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects, image: AppAssets.markAllAsReadMenuImage) } func deleteMenuItem(_ objects: [Any]) -> NSMenuItem { - return menuItem(NSLocalizedString("Delete", comment: "Command"), #selector(deleteFromContextualMenu(_:)), objects) + return menuItem(NSLocalizedString("Delete", comment: "Command"), #selector(deleteFromContextualMenu(_:)), objects, image: AppAssets.deleteImage) } func renameMenuItem(_ object: Any) -> NSMenuItem { - return menuItem(NSLocalizedString("Rename", comment: "Command"), #selector(renameFromContextualMenu(_:)), object) + return menuItem(NSLocalizedString("Rename", comment: "Command"), #selector(renameFromContextualMenu(_:)), object, image: AppAssets.renameImage) } func anyObjectInArrayHasNonZeroUnreadCount(_ objects: [Any]) -> Bool { @@ -341,11 +341,14 @@ private extension SidebarViewController { return object is WebFeed || object is Folder } - func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem { + func menuItem(_ title: String, _ action: Selector, _ representedObject: Any, image: RSImage?) -> NSMenuItem { let item = NSMenuItem(title: title, action: action, keyEquivalent: "") item.representedObject = representedObject item.target = self + if let image { + item.image = image + } return item } diff --git a/Mac/MainWindow/Sidebar/UnreadCountView.swift b/Mac/MainWindow/Sidebar/UnreadCountView.swift index 25ee1c2a4..75ab94e6b 100644 --- a/Mac/MainWindow/Sidebar/UnreadCountView.swift +++ b/Mac/MainWindow/Sidebar/UnreadCountView.swift @@ -13,11 +13,9 @@ class UnreadCountView : NSView { struct Appearance { static let padding = NSEdgeInsets(top: 1.0, left: 7.0, bottom: 1.0, right: 7.0) static let cornerRadius: CGFloat = 8.0 - static let backgroundColor = NSColor(named: "SidebarUnreadCountBackground")! - static let textColor = NSColor(named: "SidebarUnreadCountText")! - static let textSize: CGFloat = 11.0 - static let textFont = NSFont.systemFont(ofSize: textSize, weight: NSFont.Weight.semibold) - static let textAttributes: [NSAttributedString.Key: AnyObject] = [NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.font: textFont, NSAttributedString.Key.kern: NSNull()] + static let backgroundColor = NSColor.clear + static let textSize: CGFloat = 13.0 + static let textFont = NSFont.systemFont(ofSize: textSize, weight: NSFont.Weight.regular) } var unreadCount = 0 { @@ -30,6 +28,24 @@ class UnreadCountView : NSView { return unreadCount < 1 ? "" : "\(unreadCount)" } + var isSelected: Bool = false { + didSet { + needsDisplay = true + } + } + + private var currentTextColor: NSColor { + return isSelected ? NSColor.white : NSColor.tertiaryLabelColor + } + + private var textAttributes: [NSAttributedString.Key: AnyObject] { + return [ + .foregroundColor: currentTextColor, + .font: Appearance.textFont, + .kern: NSNull() + ] + } + private var intrinsicContentSizeIsValid = false private var _intrinsicContentSize = NSZeroSize @@ -66,7 +82,7 @@ class UnreadCountView : NSView { return cachedSize } - var size = unreadCountString.size(withAttributes: Appearance.textAttributes) + var size = unreadCountString.size(withAttributes: textAttributes) size.height = ceil(size.height) size.width = ceil(size.width) @@ -89,7 +105,7 @@ class UnreadCountView : NSView { path.fill() if unreadCount > 0 { - unreadCountString.draw(at: textRect().origin, withAttributes: Appearance.textAttributes) + unreadCountString.draw(at: textRect().origin, withAttributes: textAttributes) } } } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift index dbfdf045b..83e5a81c5 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift @@ -56,11 +56,7 @@ struct TimelineCellAppearance: Equatable { self.showIcon = showIcon - if #available(macOS 11.0, *) { - cellPadding = NSEdgeInsets(top: 8.0, left: 4.0, bottom: 10.0, right: 4.0) - } else { - cellPadding = NSEdgeInsets(top: 8.0, left: 18.0, bottom: 10.0, right: 18.0) - } + cellPadding = NSEdgeInsets(top: 8.0, left: 4.0, bottom: 10.0, right: 4.0) let margin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight self.boxLeftMargin = margin diff --git a/Mac/MainWindow/Timeline/TimelineTableRowView.swift b/Mac/MainWindow/Timeline/TimelineTableRowView.swift index bf42f4edd..54d87479c 100644 --- a/Mac/MainWindow/Timeline/TimelineTableRowView.swift +++ b/Mac/MainWindow/Timeline/TimelineTableRowView.swift @@ -59,21 +59,12 @@ class TimelineTableRowView : NSTableRowView { separator!.wantsLayer = true separator!.layer?.backgroundColor = AppAssets.timelineSeparatorColor.cgColor addSubview(separator!) - if #available(macOS 11.0, *) { - NSLayoutConstraint.activate([ - separator!.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 20), - separator!.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: -4), - separator!.heightAnchor.constraint(equalToConstant: 1), - separator!.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0) - ]) - } else { - NSLayoutConstraint.activate([ - separator!.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 34), - separator!.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: -28), - separator!.heightAnchor.constraint(equalToConstant: 1), - separator!.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0) - ]) - } + NSLayoutConstraint.activate([ + separator!.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 20), + separator!.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: -4), + separator!.heightAnchor.constraint(equalToConstant: 1), + separator!.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0) + ]) } } diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index 1cc5d6e3e..32ce32ac0 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -222,36 +222,36 @@ private extension TimelineViewController { func markReadMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark as Read", comment: "Command"), #selector(markArticlesReadFromContextualMenu(_:)), articles) + return menuItem(NSLocalizedString("Mark as Read", comment: "Command"), #selector(markArticlesReadFromContextualMenu(_:)), articles, image: AppAssets.swipeMarkReadImage) } func markUnreadMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark as Unread", comment: "Command"), #selector(markArticlesUnreadFromContextualMenu(_:)), articles) + return menuItem(NSLocalizedString("Mark as Unread", comment: "Command"), #selector(markArticlesUnreadFromContextualMenu(_:)), articles, image: AppAssets.swipeMarkUnreadImage) } func markStarredMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark as Starred", comment: "Command"), #selector(markArticlesStarredFromContextualMenu(_:)), articles) + return menuItem(NSLocalizedString("Mark as Starred", comment: "Command"), #selector(markArticlesStarredFromContextualMenu(_:)), articles, image: AppAssets.swipeMarkStarredImage) } func markUnstarredMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark as Unstarred", comment: "Command"), #selector(markArticlesUnstarredFromContextualMenu(_:)), articles) + return menuItem(NSLocalizedString("Mark as Unstarred", comment: "Command"), #selector(markArticlesUnstarredFromContextualMenu(_:)), articles, image: AppAssets.swipeMarkUnstarredImage) } func markAboveReadMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark Above as Read", comment: "Command"), #selector(markAboveArticlesReadFromContextualMenu(_:)), articles) + return menuItem(NSLocalizedString("Mark Above as Read", comment: "Command"), #selector(markAboveArticlesReadFromContextualMenu(_:)), articles, image: nil) } func markBelowReadMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark Below as Read", comment: "Command"), #selector(markBelowArticlesReadFromContextualMenu(_:)), articles) + return menuItem(NSLocalizedString("Mark Below as Read", comment: "Command"), #selector(markBelowArticlesReadFromContextualMenu(_:)), articles, image: nil) } func selectFeedInSidebarMenuItem(_ feed: WebFeed) -> NSMenuItem { let localizedMenuText = NSLocalizedString("Select “%@” in Sidebar", comment: "Command") let formattedMenuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) - return menuItem(formattedMenuText as String, #selector(selectFeedInSidebarFromContextualMenu(_:)), feed) + return menuItem(formattedMenuText as String, #selector(selectFeedInSidebarFromContextualMenu(_:)), feed, image: nil) } func markAllAsReadMenuItem(_ feed: WebFeed) -> NSMenuItem? { @@ -266,28 +266,31 @@ private extension TimelineViewController { let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command") let menuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String - return menuItem(menuText, #selector(markAllInFeedAsRead(_:)), articles) + return menuItem(menuText, #selector(markAllInFeedAsRead(_:)), articles, image: AppAssets.markAllAsReadImage) } func openInBrowserMenuItem(_ urlString: String) -> NSMenuItem { - return menuItem(NSLocalizedString("Open in Browser", comment: "Command"), #selector(openInBrowserFromContextualMenu(_:)), urlString) + return menuItem(NSLocalizedString("Open in Browser", comment: "Command"), #selector(openInBrowserFromContextualMenu(_:)), urlString, image: AppAssets.openInBrowserImage) } func copyArticleURLMenuItem(_ urlString: String) -> NSMenuItem { - return menuItem(NSLocalizedString("Copy Article URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString) + return menuItem(NSLocalizedString("Copy Article URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString, image: AppAssets.copyImage) } func copyExternalURLMenuItem(_ urlString: String) -> NSMenuItem { - return menuItem(NSLocalizedString("Copy External URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString) + return menuItem(NSLocalizedString("Copy External URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString, image: AppAssets.copyImage) } - func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem { + func menuItem(_ title: String, _ action: Selector, _ representedObject: Any, image: RSImage?) -> NSMenuItem { let item = NSMenuItem(title: title, action: action, keyEquivalent: "") item.representedObject = representedObject item.target = self + if let image { + item.image = image + } return item } } diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index f19276b9f..bf9e886b1 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -208,9 +208,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr tableView.setDraggingSourceOperationMask(.copy, forLocal: false) tableView.keyboardDelegate = keyboardDelegate - if #available(macOS 11.0, *) { - tableView.style = .inset - } + tableView.style = .inset if !didRegisterForNotifications { NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index 444e152d0..3a4112ca4 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -133,10 +133,8 @@ class AccountsFeedbinWindowController: NSWindowController { // MARK: Autofill func enableAutofill() { - if #available(macOS 11, *) { - usernameTextField.contentType = .username - passwordTextField.contentType = .password - } + usernameTextField.contentType = .username + passwordTextField.contentType = .password } } diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index 00f47778e..9c70fceb5 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -129,10 +129,8 @@ class AccountsNewsBlurWindowController: NSWindowController { // MARK: Autofill func enableAutofill() { - if #available(macOS 11, *) { - usernameTextField.contentType = .username - passwordTextField.contentType = .password - } + usernameTextField.contentType = .username + passwordTextField.contentType = .password } } diff --git a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift index a9e831afd..846c4f9a4 100644 --- a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift @@ -197,10 +197,8 @@ class AccountsReaderAPIWindowController: NSWindowController { // MARK: Autofill func enableAutofill() { - if #available(macOS 11, *) { - usernameTextField.contentType = .username - passwordTextField.contentType = .password - } + usernameTextField.contentType = .username + passwordTextField.contentType = .password } } diff --git a/Mac/Preferences/Accounts/AddAccountsView.swift b/Mac/Preferences/Accounts/AddAccountsView.swift index c8a496ad2..e43463125 100644 --- a/Mac/Preferences/Accounts/AddAccountsView.swift +++ b/Mac/Preferences/Accounts/AddAccountsView.swift @@ -102,45 +102,23 @@ struct AddAccountsView: View { HStack(spacing: 12) { Spacer() - if #available(OSX 11.0, *) { - Button(action: { - parent?.dismiss(nil) - }, label: { - Text("Cancel") - .frame(width: 76) - }) - .help("Cancel") - .keyboardShortcut(.cancelAction) - - } else { - Button(action: { - parent?.dismiss(nil) - }, label: { - Text("Cancel") - .frame(width: 76) - }) - .accessibility(label: Text("Add Account")) - } - if #available(OSX 11.0, *) { - Button(action: { - addAccountDelegate?.presentSheetForAccount(selectedAccount) - parent?.dismiss(nil) - }, label: { - Text("Continue") - .frame(width: 76) - }) - .help("Add Account") - .keyboardShortcut(.defaultAction) - - } else { - Button(action: { - addAccountDelegate?.presentSheetForAccount(selectedAccount) - parent?.dismiss(nil) - }, label: { - Text("Continue") - .frame(width: 76) - }) - } + Button(action: { + parent?.dismiss(nil) + }, label: { + Text("Cancel") + .frame(width: 76) + }) + .help("Cancel") + .keyboardShortcut(.cancelAction) + Button(action: { + addAccountDelegate?.presentSheetForAccount(selectedAccount) + parent?.dismiss(nil) + }, label: { + Text("Continue") + .frame(width: 76) + }) + .help("Add Account") + .keyboardShortcut(.defaultAction) } .padding(.top, 12) .padding(.bottom, 4) diff --git a/Mac/Resources/Assets.xcassets/articleExtractorOff.imageset/ArticleExtractorOff.pdf b/Mac/Resources/Assets.xcassets/articleExtractorOff.imageset/ArticleExtractorOff.pdf deleted file mode 100644 index d1a8f11c8..000000000 Binary files a/Mac/Resources/Assets.xcassets/articleExtractorOff.imageset/ArticleExtractorOff.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/articleExtractorOff.imageset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorOff.imageset/Contents.json deleted file mode 100644 index a0b6f11cb..000000000 --- a/Mac/Resources/Assets.xcassets/articleExtractorOff.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "ArticleExtractorOff.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/Mac/Resources/Assets.xcassets/articleExtractorOff.symbolset/Article Extractor Off.svg b/Mac/Resources/Assets.xcassets/articleExtractorOff.symbolset/Article Extractor Off.svg new file mode 100644 index 000000000..95c2b0801 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorOff.symbolset/Article Extractor Off.svg @@ -0,0 +1,158 @@ + + + Article Extractor Off + + + + + + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + can be adjusted by modifying the width of the blue rectangles. + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.1.0 + + + Generated from circle + + + Typeset at 100 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/articleExtractorOff.symbolset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorOff.symbolset/Contents.json new file mode 100644 index 000000000..255ead96d --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorOff.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "Article Extractor Off.svg", + "idiom" : "universal" + } + ] +} diff --git a/Mac/Resources/Assets.xcassets/articleExtractorOn.imageset/ArticleExtractorOn.pdf b/Mac/Resources/Assets.xcassets/articleExtractorOn.imageset/ArticleExtractorOn.pdf deleted file mode 100644 index 59cf13d37..000000000 Binary files a/Mac/Resources/Assets.xcassets/articleExtractorOn.imageset/ArticleExtractorOn.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/articleExtractorOn.imageset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorOn.imageset/Contents.json deleted file mode 100644 index ba8f780d4..000000000 --- a/Mac/Resources/Assets.xcassets/articleExtractorOn.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "ArticleExtractorOn.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/Mac/Resources/Assets.xcassets/articleExtractorOn.symbolset/Article Extractor On.svg b/Mac/Resources/Assets.xcassets/articleExtractorOn.symbolset/Article Extractor On.svg new file mode 100644 index 000000000..de5808f1f --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorOn.symbolset/Article Extractor On.svg @@ -0,0 +1,158 @@ + + + Article Extractor On + + + + + + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + can be adjusted by modifying the width of the blue rectangles. + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.1.0 + + + Generated from circle + + + Typeset at 100 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/articleExtractorOn.symbolset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorOn.symbolset/Contents.json new file mode 100644 index 000000000..14e45223b --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorOn.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "Article Extractor On.svg", + "idiom" : "universal" + } + ] +} diff --git a/Mac/Resources/Assets.xcassets/markAllAsRead.imageset/Contents.json b/Mac/Resources/Assets.xcassets/markAllAsRead.imageset/Contents.json deleted file mode 100644 index 9937c57c5..000000000 --- a/Mac/Resources/Assets.xcassets/markAllAsRead.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "markAllAsRead.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/Mac/Resources/Assets.xcassets/markAllAsRead.imageset/markAllAsRead.pdf b/Mac/Resources/Assets.xcassets/markAllAsRead.imageset/markAllAsRead.pdf deleted file mode 100644 index 420f9d49c..000000000 Binary files a/Mac/Resources/Assets.xcassets/markAllAsRead.imageset/markAllAsRead.pdf and /dev/null differ diff --git a/Mac/Resources/Assets.xcassets/markAllAsRead.symbolset/Contents.json b/Mac/Resources/Assets.xcassets/markAllAsRead.symbolset/Contents.json new file mode 100644 index 000000000..5a1bb0402 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/markAllAsRead.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "markAsRead3.svg", + "idiom" : "universal" + } + ] +} diff --git a/Mac/Resources/Assets.xcassets/markAllAsRead.symbolset/markAsRead3.svg b/Mac/Resources/Assets.xcassets/markAllAsRead.symbolset/markAsRead3.svg new file mode 100644 index 000000000..070af7a07 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/markAllAsRead.symbolset/markAsRead3.svg @@ -0,0 +1,150 @@ + + + markAllAsRead2 + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + can be adjusted by modifying the width of the blue rectangles. + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.1.0 + + + Generated from circle + + + Typeset at 100 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 75c4065b9..41dd76ac5 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -1464,11 +1464,6 @@ baseConfigurationReferenceAnchor = 84719F372DB9C60400EEF332 /* xcconfig */; baseConfigurationReferenceRelativePath = NetNewsWire_iOSapp_target.xcconfig; buildSettings = { - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -1477,11 +1472,6 @@ baseConfigurationReferenceAnchor = 84719F372DB9C60400EEF332 /* xcconfig */; baseConfigurationReferenceRelativePath = NetNewsWire_iOSapp_target.xcconfig; buildSettings = { - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; diff --git a/Shared/Extensions/NSView-Extensions.swift b/Shared/Extensions/NSView-Extensions.swift index e9449208e..2c5aff1d0 100644 --- a/Shared/Extensions/NSView-Extensions.swift +++ b/Shared/Extensions/NSView-Extensions.swift @@ -11,20 +11,10 @@ import AppKit extension NSView { func constraintsToMakeSubViewFullSize(_ subview: NSView) -> [NSLayoutConstraint] { - - if #available(macOS 11, *) { - let leadingConstraint = NSLayoutConstraint(item: subview, attribute: .leading, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .leading, multiplier: 1.0, constant: 0.0) - let trailingConstraint = NSLayoutConstraint(item: subview, attribute: .trailing, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .trailing, multiplier: 1.0, constant: 0.0) - let topConstraint = NSLayoutConstraint(item: subview, attribute: .top, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .top, multiplier: 1.0, constant: 0.0) - let bottomConstraint = NSLayoutConstraint(item: subview, attribute: .bottom, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0.0) - return [leadingConstraint, trailingConstraint, topConstraint, bottomConstraint] - } else { - let leadingConstraint = NSLayoutConstraint(item: subview, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0) - let trailingConstraint = NSLayoutConstraint(item: subview, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0) - let topConstraint = NSLayoutConstraint(item: subview, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0) - let bottomConstraint = NSLayoutConstraint(item: subview, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0) - return [leadingConstraint, trailingConstraint, topConstraint, bottomConstraint] - } - + let leadingConstraint = NSLayoutConstraint(item: subview, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0) + let trailingConstraint = NSLayoutConstraint(item: subview, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0) + let topConstraint = NSLayoutConstraint(item: subview, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0) + let bottomConstraint = NSLayoutConstraint(item: subview, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0) + return [leadingConstraint, trailingConstraint, topConstraint, bottomConstraint] } } diff --git a/iOS/MainFeed/MainFeedViewController.swift b/iOS/MainFeed/MainFeedViewController.swift index 829a91265..eead0a72e 100644 --- a/iOS/MainFeed/MainFeedViewController.swift +++ b/iOS/MainFeed/MainFeedViewController.swift @@ -18,7 +18,6 @@ import SafariServices class MainFeedViewController: UITableViewController, UndoableCommandRunner { @IBOutlet weak var filterButton: UIBarButtonItem! - private var refreshProgressView: RefreshProgressView? @IBOutlet weak var addNewItemButton: UIBarButtonItem! { didSet { if #available(iOS 14, *) { @@ -72,7 +71,6 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .combinedRefreshProgressDidChange, object: nil) registerForTraitChanges([UITraitPreferredContentSizeCategory.self], target: self, action: #selector(preferredContentSizeCategoryDidChange)) @@ -81,7 +79,7 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { } override func viewWillAppear(_ animated: Bool) { - navigationController?.isToolbarHidden = false + navigationController?.isToolbarHidden = false updateUI() super.viewWillAppear(animated) @@ -180,10 +178,6 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { updateUI() } - @objc func progressDidChange(_ note: Notification) { - updateNavigationBarSubtitle() - } - // MARK: Table View override func numberOfSections(in tableView: UITableView) -> Int { @@ -661,11 +655,9 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { } else { setFilterButtonToInactive() } - refreshProgressView?.update() addNewItemButton?.isEnabled = !AccountManager.shared.activeAccounts.isEmpty configureContextMenu() - updateNavigationBarSubtitle() } @objc @@ -699,37 +691,7 @@ class MainFeedViewController: UITableViewController, UndoableCommandRunner { } } - func updateNavigationBarSubtitle() { - let progress = AccountManager.shared.combinedRefreshProgress - - if progress.isComplete { - if let accountLastArticleFetchEndTime = AccountManager.shared.lastArticleFetchEndTime { - if Date() > accountLastArticleFetchEndTime.addingTimeInterval(60) { - let relativeDateTimeFormatter = RelativeDateTimeFormatter() - relativeDateTimeFormatter.dateTimeStyle = .named - let refreshed = relativeDateTimeFormatter.localizedString(for: accountLastArticleFetchEndTime, relativeTo: Date()) - let localizedRefreshText = NSLocalizedString("Updated %@", comment: "Updated") - let refreshText = NSString.localizedStringWithFormat(localizedRefreshText as NSString, refreshed) as String - navigationController?.navigationBar.topItem?.subtitle = refreshText - } else { - navigationController?.navigationBar.topItem?.subtitle = NSLocalizedString("Updated Just Now", comment: "Updated Just Now") - } - } else { - navigationController?.navigationBar.topItem?.subtitle = "" - } - - } else { - navigationController?.navigationBar.topItem?.subtitle = NSLocalizedString("Updating...", comment: "Updating...") - } - - scheduleNavigationBarSubtitleUpdate() - } - func scheduleNavigationBarSubtitleUpdate() { - DispatchQueue.main.asyncAfter(deadline: .now() + 60) { [weak self] in - self?.updateNavigationBarSubtitle() - } - } func focus() { becomeFirstResponder() diff --git a/iOS/MainTimeline/MainTimelineTitleView.swift b/iOS/MainTimeline/MainTimelineTitleView.swift index 7a68a4493..3c4ca49df 100644 --- a/iOS/MainTimeline/MainTimelineTitleView.swift +++ b/iOS/MainTimeline/MainTimelineTitleView.swift @@ -33,7 +33,7 @@ class MainTimelineTitleView: UIView { } func buttonize() { - heightAnchor.constraint(equalToConstant: 40.0).isActive = true + heightAnchor.constraint(equalToConstant: 25.0).isActive = true accessibilityTraits = .button if #available(iOS 13.4, *) { addInteraction(pointerInteraction) @@ -41,7 +41,7 @@ class MainTimelineTitleView: UIView { } func debuttonize() { - heightAnchor.constraint(equalToConstant: 40.0).isActive = true + heightAnchor.constraint(equalToConstant: 25.0).isActive = true accessibilityTraits.remove(.button) if #available(iOS 13.4, *) { removeInteraction(pointerInteraction) diff --git a/iOS/MainTimeline/MainTimelineTitleView.xib b/iOS/MainTimeline/MainTimelineTitleView.xib index 9ea9ed250..2c75c4244 100644 --- a/iOS/MainTimeline/MainTimelineTitleView.xib +++ b/iOS/MainTimeline/MainTimelineTitleView.xib @@ -1,9 +1,8 @@ - + - - + @@ -11,38 +10,43 @@ - + - - - - - - - + + + + + + + + + + + + + - - + + + + + + - - - - - - - + + @@ -50,7 +54,6 @@ - diff --git a/iOS/MainTimeline/MainTimelineViewController.swift b/iOS/MainTimeline/MainTimelineViewController.swift index a78866f4a..42c90e8f8 100644 --- a/iOS/MainTimeline/MainTimelineViewController.swift +++ b/iOS/MainTimeline/MainTimelineViewController.swift @@ -195,14 +195,13 @@ class MainTimelineViewController: UITableViewController, UndoableCommandRunner { if navigationController?.navigationBar.isHidden ?? false { navigationController?.navigationBar.alpha = 0 } - navigationController?.navigationBar.topItem?.subtitle = nil + super.viewWillAppear(animated) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(true) isTimelineViewControllerPending = false - if navigationController?.navigationBar.alpha == 0 { UIView.animate(withDuration: 0.5) { self.navigationController?.navigationBar.alpha = 1 diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 5475255ed..71c9e5efd 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -39,6 +39,7 @@ struct FeedNode: Hashable { } } + class SceneCoordinator: NSObject, UndoableCommandRunner { var undoableCommands = [UndoableCommand]() @@ -83,6 +84,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { var isTimelineViewControllerPending = false var isArticleViewControllerPending = false + /// `Bool` to track whether a refresh is scheduled. + private var isRefreshScheduled: Bool = false + private(set) var sortDirection = AppDefaults.shared.timelineSortDirection { didSet { if sortDirection != oldValue { @@ -272,7 +276,11 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { return appDelegate.unreadCount > 0 } - var timelineUnreadCount: Int = 0 + var timelineUnreadCount: Int = 0 { + didSet { + updateNavigationBarSubtitles(nil) + } + } init(rootSplitViewController: RootSplitViewController) { self.rootSplitViewController = rootSplitViewController @@ -312,6 +320,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(importDownloadedTheme(_:)), name: .didEndDownloadingTheme, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(themeDownloadDidFail(_:)), name: .didFailToImportThemeWithError, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(updateNavigationBarSubtitles(_:)), name: .combinedRefreshProgressDidChange, object: nil) + } func restoreWindowState(_ activity: NSUserActivity?) { @@ -539,6 +549,94 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { self.rootSplitViewController.presentError(error, dismiss: nil) } } + + + /// Updates navigation bar subtitles in response to feed selection, unread count changes, + /// `combinedRefreshProgressDidChange` notifications, and a timed refresh every + /// 60s. + /// + /// Subtitles are handled differently on iPhone and iPad. + /// + /// `MainFeedViewController` + /// - When refreshing: Feeds will display "Updating..." on both iPhone and iPad. + /// - When refreshed: Feeds will display "Updated <#relative_time#>" on both iPhone and iPad. + /// + /// `MainTimelineViewController` + /// - Where the unread count for the timeline is > 0, this is displayed on both iPhone and iPad. + /// - If the timeline count is 0, the iPhone follows the same logic as `MainFeedViewController` + /// - Specific to iPad, if the unread count is 0, the iPad will not display a subtitle. The refresh text + /// will generally be visible in the sidebar and there's no need to display it twice. + /// + /// - Parameter note: Optional `Notification` + @objc func updateNavigationBarSubtitles(_ note: Notification?) { + let progress = AccountManager.shared.combinedRefreshProgress + + if progress.isComplete { + if let accountLastArticleFetchEndTime = AccountManager.shared.lastArticleFetchEndTime { + if Date.now > accountLastArticleFetchEndTime.addingTimeInterval(60) { + let relativeDateTimeFormatter = RelativeDateTimeFormatter() + relativeDateTimeFormatter.dateTimeStyle = .named + let refreshed = relativeDateTimeFormatter.localizedString(for: accountLastArticleFetchEndTime, relativeTo: Date()) + let localizedRefreshText = NSLocalizedString("Updated %@", comment: "Updated") + let refreshText = NSString.localizedStringWithFormat(localizedRefreshText as NSString, refreshed) as String + + // Update Feeds with Updated text + self.mainFeedViewController?.navigationItem.subtitle = refreshText + + // If unread count > 0, add unread string to timeline + if let _ = timelineFeed, timelineUnreadCount > 0 { + let localizedUnreadCount = NSLocalizedString("%i Unread", comment: "14 Unread") + let unreadCount = NSString.localizedStringWithFormat(localizedUnreadCount as NSString, timelineUnreadCount) as String + self.mainTimelineViewController?.navigationItem.subtitle = unreadCount + } else { + // When unread count == 0, iPhone timeline displays Updated Just Now; iPad is blank + if UIDevice.current.userInterfaceIdiom == .phone { + self.mainTimelineViewController?.navigationItem.subtitle = refreshText + } else { + self.mainTimelineViewController?.navigationItem.subtitle = "" + } + } + } else { + // Use 'Updated Just Now' while <60s have passed since refresh. + self.mainFeedViewController?.navigationItem.subtitle = NSLocalizedString("Updated Just Now", comment: "Updated Just Now") + + // If unread count > 0, add unread string to timeline + if let _ = timelineFeed, timelineUnreadCount > 0 { + let localizedUnreadCount = NSLocalizedString("%i Unread", comment: "14 Unread") + let refreshTextWithUnreadCount = NSString.localizedStringWithFormat(localizedUnreadCount as NSString, timelineUnreadCount) as String + self.mainTimelineViewController?.navigationItem.subtitle = refreshTextWithUnreadCount + } else { + // When unread count == 0, iPhone timeline displays Updated Just Now; iPad is blank + if UIDevice.current.userInterfaceIdiom == .phone { + self.mainTimelineViewController?.navigationItem.subtitle = NSLocalizedString("Updated Just Now", comment: "Updated Just Now") + } else { + self.mainTimelineViewController?.navigationItem.subtitle = "" + } + } + } + } else { + self.mainFeedViewController?.navigationItem.subtitle = "" + self.mainTimelineViewController?.navigationItem.subtitle = "" + } + } else { + // Updating in progress, apply to both iPhone and iPad Feeds. + self.mainFeedViewController?.navigationItem.subtitle = NSLocalizedString("Updating...", comment: "Updating...") + } + + scheduleNavigationBarSubtitleUpdate() + + } + + func scheduleNavigationBarSubtitleUpdate() { + if isRefreshScheduled { + return + } + isRefreshScheduled = true + DispatchQueue.main.asyncAfter(deadline: .now() + 60) { [weak self] in + self?.isRefreshScheduled = false + self?.updateNavigationBarSubtitles(nil) + } + } // MARK: API @@ -735,6 +833,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } }() selectFeed(indexPath: indexPath, animations: animations, deselectArticle: deselectArticle, completion: completion) + updateNavigationBarSubtitles(nil) } func selectFeed(indexPath: IndexPath?, animations: Animations = [], deselectArticle: Bool = true, completion: (() -> Void)? = nil) { @@ -773,6 +872,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } } + updateNavigationBarSubtitles(nil) } @@ -2212,4 +2312,5 @@ private extension SceneCoordinator { return true } + } diff --git a/xcconfig/NetNewsWire_iOSapp_target.xcconfig b/xcconfig/NetNewsWire_iOSapp_target.xcconfig index 3c5f63c71..cd5380c77 100644 --- a/xcconfig/NetNewsWire_iOSapp_target.xcconfig +++ b/xcconfig/NetNewsWire_iOSapp_target.xcconfig @@ -44,3 +44,8 @@ PRODUCT_NAME = NetNewsWire CLANG_ENABLE_MODULES = YES SWIFT_OBJC_BRIDGING_HEADER = iOS/Resources/NetNewsWire-iOS-Bridging-Header.h SWIFT_VERSION = 5.7 +SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; +SUPPORTS_MACCATALYST = NO; +SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; +SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; +TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/xcconfig/NetNewsWire_macapp_target.xcconfig b/xcconfig/NetNewsWire_macapp_target.xcconfig index 600a8a2a1..13d4b1a36 100644 --- a/xcconfig/NetNewsWire_macapp_target.xcconfig +++ b/xcconfig/NetNewsWire_macapp_target.xcconfig @@ -37,3 +37,4 @@ DEVELOPER_ENTITLEMENTS = CODE_SIGN_ENTITLEMENTS = Mac/Resources/NetNewsWire$(DEVELOPER_ENTITLEMENTS).entitlements PRODUCT_BUNDLE_IDENTIFIER = $(ORGANIZATION_IDENTIFIER).NetNewsWire-Evergreen +MACOSX_DEPLOYMENT_TARGET = 26.0