Get Mac and iOS builds building.
This commit is contained in:
parent
83e3324a4a
commit
59af6041ca
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -168,15 +168,6 @@ public final class Feed: SidebarItem, Renamable, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public var sinceToken: String? {
|
||||
get {
|
||||
return metadata.sinceToken
|
||||
}
|
||||
set {
|
||||
metadata.sinceToken = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public var externalID: String? {
|
||||
get {
|
||||
return metadata.externalID
|
||||
|
@ -216,7 +216,7 @@ private extension LocalAccountRefresher {
|
||||
if let url = urlCache[urlString] {
|
||||
return url
|
||||
}
|
||||
if let url = URL(unicodeString: urlString) {
|
||||
if let url = URL(string: urlString) {
|
||||
urlCache[urlString] = url
|
||||
return url
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -779,7 +779,8 @@ internal extension AppDelegate {
|
||||
guard let window = mainWindowController?.window else { return }
|
||||
|
||||
do {
|
||||
let theme = try ArticleTheme(path: filename, isAppTheme: false)
|
||||
let themeURL = URL(filePath: filename)
|
||||
let theme = try ArticleTheme(url: themeURL, isAppTheme: false)
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .informational
|
||||
|
||||
|
@ -75,8 +75,8 @@ protocol SidebarDelegate: AnyObject {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .feedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .feedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
|
||||
DistributedNotificationCenter.default().addObserver(self, selector: #selector(appleSideBarDefaultIconSizeChanged(_:)), name: .appleSideBarDefaultIconSizeChanged, object: nil)
|
||||
|
||||
@ -153,7 +153,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
return
|
||||
}
|
||||
|
||||
if let timelineViewController = representedObject as? MainTimelineViewController {
|
||||
if let timelineViewController = representedObject as? TimelineViewController {
|
||||
configureUnreadCountForCellsForRepresentedObjects(timelineViewController.representedObjects)
|
||||
} else {
|
||||
configureUnreadCountForCellsForRepresentedObjects([representedObject as AnyObject])
|
||||
|
@ -1,79 +0,0 @@
|
||||
//
|
||||
// TimelineCellData.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 2/6/16.
|
||||
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Articles
|
||||
|
||||
struct MainTimelineCellData {
|
||||
|
||||
private static let noText = NSLocalizedString("(No Text)", comment: "No Text")
|
||||
|
||||
let title: String
|
||||
let attributedTitle: NSAttributedString
|
||||
let text: String
|
||||
let dateString: String
|
||||
let feedName: String
|
||||
let byline: String
|
||||
let showFeedName: TimelineShowFeedName
|
||||
let iconImage: IconImage? // feed icon, user avatar, or favicon
|
||||
let showIcon: Bool // Make space even when icon is nil
|
||||
let featuredImage: NSImage? // image from within the article
|
||||
let read: Bool
|
||||
let starred: Bool
|
||||
|
||||
init(article: Article, showFeedName: TimelineShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: NSImage?) {
|
||||
|
||||
self.title = ArticleStringFormatter.truncatedTitle(article)
|
||||
self.attributedTitle = ArticleStringFormatter.attributedTruncatedTitle(article)
|
||||
|
||||
let truncatedSummary = ArticleStringFormatter.truncatedSummary(article)
|
||||
if self.title.isEmpty && truncatedSummary.isEmpty {
|
||||
self.text = Self.noText
|
||||
} else {
|
||||
self.text = truncatedSummary
|
||||
}
|
||||
|
||||
self.dateString = ArticleStringFormatter.dateString(article.logicalDatePublished)
|
||||
|
||||
if let feedName = feedName {
|
||||
self.feedName = ArticleStringFormatter.truncatedFeedName(feedName)
|
||||
} else {
|
||||
self.feedName = ""
|
||||
}
|
||||
|
||||
if let byline = byline {
|
||||
self.byline = byline
|
||||
} else {
|
||||
self.byline = ""
|
||||
}
|
||||
|
||||
self.showFeedName = showFeedName
|
||||
|
||||
self.showIcon = showIcon
|
||||
self.iconImage = iconImage
|
||||
self.featuredImage = featuredImage
|
||||
|
||||
self.read = article.status.read
|
||||
self.starred = article.status.starred
|
||||
}
|
||||
|
||||
init() { //Empty
|
||||
self.title = ""
|
||||
self.text = ""
|
||||
self.dateString = ""
|
||||
self.feedName = ""
|
||||
self.byline = ""
|
||||
self.showFeedName = .none
|
||||
self.showIcon = false
|
||||
self.iconImage = nil
|
||||
self.featuredImage = nil
|
||||
self.read = true
|
||||
self.starred = false
|
||||
self.attributedTitle = NSAttributedString()
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
//
|
||||
// UnreadIndicatorView.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 2/16/16.
|
||||
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
class MainUnreadIndicatorView: NSView {
|
||||
|
||||
static let unreadCircleDimension: CGFloat = 8.0
|
||||
|
||||
var isEmphasized = false {
|
||||
didSet {
|
||||
if isEmphasized != oldValue {
|
||||
needsDisplay = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSelected = false {
|
||||
didSet {
|
||||
if isSelected != oldValue {
|
||||
needsDisplay = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static let bezierPath: NSBezierPath = {
|
||||
let r = NSRect(x: 0.0, y: 0.0, width: unreadCircleDimension, height: unreadCircleDimension)
|
||||
return NSBezierPath(ovalIn: r)
|
||||
}()
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
if isSelected && isEmphasized {
|
||||
NSColor.white.setFill()
|
||||
} else {
|
||||
NSColor.controlAccentColor.setFill()
|
||||
}
|
||||
MainUnreadIndicatorView.bezierPath.fill()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
//
|
||||
// TimelineCellData.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 2/6/16.
|
||||
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Articles
|
||||
|
||||
struct TimelineCellData {
|
||||
|
||||
private static let noText = NSLocalizedString("(No Text)", comment: "No Text")
|
||||
|
||||
let title: String
|
||||
let attributedTitle: NSAttributedString
|
||||
let text: String
|
||||
let dateString: String
|
||||
let feedName: String
|
||||
let byline: String
|
||||
let showFeedName: TimelineShowFeedName
|
||||
let iconImage: IconImage? // feed icon, user avatar, or favicon
|
||||
let showIcon: Bool // Make space even when icon is nil
|
||||
let featuredImage: NSImage? // image from within the article
|
||||
let read: Bool
|
||||
let starred: Bool
|
||||
|
||||
init(article: Article, showFeedName: TimelineShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: NSImage?) {
|
||||
|
||||
self.title = ArticleStringFormatter.truncatedTitle(article)
|
||||
self.attributedTitle = ArticleStringFormatter.attributedTruncatedTitle(article)
|
||||
|
||||
let truncatedSummary = ArticleStringFormatter.truncatedSummary(article)
|
||||
if self.title.isEmpty && truncatedSummary.isEmpty {
|
||||
self.text = Self.noText
|
||||
} else {
|
||||
self.text = truncatedSummary
|
||||
}
|
||||
|
||||
self.dateString = ArticleStringFormatter.dateString(article.logicalDatePublished)
|
||||
|
||||
if let feedName = feedName {
|
||||
self.feedName = ArticleStringFormatter.truncatedFeedName(feedName)
|
||||
} else {
|
||||
self.feedName = ""
|
||||
}
|
||||
|
||||
if let byline = byline {
|
||||
self.byline = byline
|
||||
} else {
|
||||
self.byline = ""
|
||||
}
|
||||
|
||||
self.showFeedName = showFeedName
|
||||
|
||||
self.showIcon = showIcon
|
||||
self.iconImage = iconImage
|
||||
self.featuredImage = featuredImage
|
||||
|
||||
self.read = article.status.read
|
||||
self.starred = article.status.starred
|
||||
}
|
||||
|
||||
init() { //Empty
|
||||
self.title = ""
|
||||
self.text = ""
|
||||
self.dateString = ""
|
||||
self.feedName = ""
|
||||
self.byline = ""
|
||||
self.showFeedName = .none
|
||||
self.showIcon = false
|
||||
self.iconImage = nil
|
||||
self.featuredImage = nil
|
||||
self.read = true
|
||||
self.starred = false
|
||||
self.attributedTitle = NSAttributedString()
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ struct TimelineCellLayout {
|
||||
}
|
||||
}
|
||||
|
||||
init(width: CGFloat, height: CGFloat, cellData: MainTimelineCellData, appearance: TimelineCellAppearance, hasIcon: Bool) {
|
||||
init(width: CGFloat, height: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance, hasIcon: Bool) {
|
||||
|
||||
// If height == 0.0, then height is calculated.
|
||||
|
||||
@ -82,7 +82,7 @@ struct TimelineCellLayout {
|
||||
self.init(width: width, height: height, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, numberOfLinesForTitle: numberOfLinesForTitle, summaryRect: summaryRect, textRect: textRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, iconImageRect: iconImageRect, separatorRect: separatorRect, paddingBottom: paddingBottom)
|
||||
}
|
||||
|
||||
static func height(for width: CGFloat, cellData: MainTimelineCellData, appearance: TimelineCellAppearance) -> CGFloat {
|
||||
static func height(for width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> CGFloat {
|
||||
|
||||
let layout = TimelineCellLayout(width: width, height: 0.0, cellData: cellData, appearance: appearance, hasIcon: true)
|
||||
return layout.height
|
||||
@ -93,7 +93,7 @@ struct TimelineCellLayout {
|
||||
|
||||
private extension TimelineCellLayout {
|
||||
|
||||
static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: MainTimelineCellData, _ showIcon: Bool, _ width: CGFloat) -> NSRect {
|
||||
static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ showIcon: Bool, _ width: CGFloat) -> NSRect {
|
||||
|
||||
// Returned height is a placeholder. Not needed when this is calculated.
|
||||
|
||||
@ -106,7 +106,7 @@ private extension TimelineCellLayout {
|
||||
return textBoxRect
|
||||
}
|
||||
|
||||
static func rectForTitle(_ textBoxRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: MainTimelineCellData) -> (NSRect, Int) {
|
||||
static func rectForTitle(_ textBoxRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> (NSRect, Int) {
|
||||
|
||||
var r = textBoxRect
|
||||
|
||||
@ -124,7 +124,7 @@ private extension TimelineCellLayout {
|
||||
return (r, sizeInfo.numberOfLinesUsed)
|
||||
}
|
||||
|
||||
static func rectForSummary(_ textBoxRect: NSRect, _ titleRect: NSRect, _ titleNumberOfLines: Int, _ appearance: TimelineCellAppearance, _ cellData: MainTimelineCellData) -> NSRect {
|
||||
static func rectForSummary(_ textBoxRect: NSRect, _ titleRect: NSRect, _ titleNumberOfLines: Int, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
|
||||
if titleNumberOfLines >= appearance.titleNumberOfLines || cellData.text.isEmpty {
|
||||
return NSRect.zero
|
||||
}
|
||||
@ -142,7 +142,7 @@ private extension TimelineCellLayout {
|
||||
|
||||
}
|
||||
|
||||
static func rectForText(_ textBoxRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: MainTimelineCellData) -> NSRect {
|
||||
static func rectForText(_ textBoxRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
|
||||
var r = textBoxRect
|
||||
|
||||
if cellData.text.isEmpty {
|
||||
@ -158,7 +158,7 @@ private extension TimelineCellLayout {
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForDate(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ appearance: TimelineCellAppearance, _ cellData: MainTimelineCellData) -> NSRect {
|
||||
static func rectForDate(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
|
||||
let textFieldSize = SingleLineTextFieldSizer.size(for: cellData.dateString, font: appearance.dateFont)
|
||||
|
||||
var r = NSZeroRect
|
||||
@ -171,7 +171,7 @@ private extension TimelineCellLayout {
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForFeedName(_ textBoxRect: NSRect, _ dateRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: MainTimelineCellData) -> NSRect {
|
||||
static func rectForFeedName(_ textBoxRect: NSRect, _ dateRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
|
||||
if cellData.showFeedName == .none {
|
||||
return NSZeroRect
|
||||
}
|
||||
@ -208,7 +208,7 @@ private extension TimelineCellLayout {
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForIcon(_ cellData: MainTimelineCellData, _ appearance: TimelineCellAppearance, _ showIcon: Bool, _ textBoxRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
|
||||
static func rectForIcon(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ showIcon: Bool, _ textBoxRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
|
||||
|
||||
var r = NSRect.zero
|
||||
if !showIcon {
|
||||
@ -221,7 +221,7 @@ private extension TimelineCellLayout {
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForSeparator(_ cellData: MainTimelineCellData, _ appearance: TimelineCellAppearance, _ alignmentRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
|
||||
static func rectForSeparator(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ alignmentRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
|
||||
return NSRect(x: alignmentRect.minX, y: height - 1, width: width - alignmentRect.minX, height: 1)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ class TimelineTableCellView: NSTableCellView {
|
||||
private let titleView = TimelineTableCellView.multiLineTextField()
|
||||
private let summaryView = TimelineTableCellView.multiLineTextField()
|
||||
private let textView = TimelineTableCellView.multiLineTextField()
|
||||
private let unreadIndicatorView = MainUnreadIndicatorView(frame: NSZeroRect)
|
||||
private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
|
||||
private let dateView = TimelineTableCellView.singleLineTextField()
|
||||
private let feedNameView = TimelineTableCellView.singleLineTextField()
|
||||
|
||||
@ -36,7 +36,7 @@ class TimelineTableCellView: NSTableCellView {
|
||||
}
|
||||
}
|
||||
|
||||
var cellData: MainTimelineCellData! {
|
||||
var cellData: TimelineCellData! {
|
||||
didSet {
|
||||
updateSubviews()
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
//
|
||||
// UnreadIndicatorView.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 2/16/16.
|
||||
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
class UnreadIndicatorView: NSView {
|
||||
|
||||
static let unreadCircleDimension: CGFloat = 8.0
|
||||
|
||||
var isEmphasized = false {
|
||||
didSet {
|
||||
if isEmphasized != oldValue {
|
||||
needsDisplay = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSelected = false {
|
||||
didSet {
|
||||
if isSelected != oldValue {
|
||||
needsDisplay = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static let bezierPath: NSBezierPath = {
|
||||
let r = NSRect(x: 0.0, y: 0.0, width: unreadCircleDimension, height: unreadCircleDimension)
|
||||
return NSBezierPath(ovalIn: r)
|
||||
}()
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
if isSelected && isEmphasized {
|
||||
NSColor.white.setFill()
|
||||
} else {
|
||||
NSColor.controlAccentColor.setFill()
|
||||
}
|
||||
UnreadIndicatorView.bezierPath.fill()
|
||||
}
|
||||
|
||||
}
|
@ -13,7 +13,7 @@ import RSCore
|
||||
|
||||
@objc final class TimelineKeyboardDelegate: NSObject, KeyboardDelegate {
|
||||
|
||||
@IBOutlet weak var timelineViewController: MainTimelineViewController?
|
||||
@IBOutlet weak var timelineViewController: TimelineViewController?
|
||||
let shortcuts: Set<KeyboardShortcut>
|
||||
|
||||
override init() {
|
||||
|
@ -27,7 +27,7 @@ final class TimelineContainerViewController: NSViewController {
|
||||
@IBOutlet weak var readFilteredButton: NSButton!
|
||||
@IBOutlet var containerView: TimelineContainerView!
|
||||
|
||||
var currentTimelineViewController: MainTimelineViewController? {
|
||||
var currentTimelineViewController: TimelineViewController? {
|
||||
didSet {
|
||||
let view = currentTimelineViewController?.view
|
||||
if containerView.contentView === view {
|
||||
@ -51,10 +51,10 @@ final class TimelineContainerViewController: NSViewController {
|
||||
}
|
||||
|
||||
lazy var regularTimelineViewController = {
|
||||
return MainTimelineViewController(delegate: self)
|
||||
return TimelineViewController(delegate: self)
|
||||
}()
|
||||
private lazy var searchTimelineViewController: MainTimelineViewController = {
|
||||
let viewController = MainTimelineViewController(delegate: self)
|
||||
private lazy var searchTimelineViewController: TimelineViewController = {
|
||||
let viewController = TimelineViewController(delegate: self)
|
||||
viewController.showsSearchResults = true
|
||||
return viewController
|
||||
}()
|
||||
@ -137,15 +137,15 @@ final class TimelineContainerViewController: NSViewController {
|
||||
|
||||
extension TimelineContainerViewController: TimelineDelegate {
|
||||
|
||||
func timelineSelectionDidChange(_ timelineViewController: MainTimelineViewController, selectedArticles: [Article]?) {
|
||||
func timelineSelectionDidChange(_ timelineViewController: TimelineViewController, selectedArticles: [Article]?) {
|
||||
delegate?.timelineSelectionDidChange(self, articles: selectedArticles, mode: mode(for: timelineViewController))
|
||||
}
|
||||
|
||||
func timelineRequestedFeedSelection(_: MainTimelineViewController, feed: Feed) {
|
||||
func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed) {
|
||||
delegate?.timelineRequestedFeedSelection(self, feed: feed)
|
||||
}
|
||||
|
||||
func timelineInvalidatedRestorationState(_: MainTimelineViewController) {
|
||||
func timelineInvalidatedRestorationState(_: TimelineViewController) {
|
||||
delegate?.timelineInvalidatedRestorationState(self)
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ private extension TimelineContainerViewController {
|
||||
attributes: [NSAttributedString.Key.font: NSFont.controlContentFont(ofSize: NSFont.systemFontSize)])
|
||||
}
|
||||
|
||||
func timelineViewController(for mode: TimelineSourceMode) -> MainTimelineViewController {
|
||||
func timelineViewController(for mode: TimelineSourceMode) -> TimelineViewController {
|
||||
switch mode {
|
||||
case .regular:
|
||||
return regularTimelineViewController
|
||||
@ -167,7 +167,7 @@ private extension TimelineContainerViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func mode(for timelineViewController: MainTimelineViewController) -> TimelineSourceMode {
|
||||
func mode(for timelineViewController: TimelineViewController) -> TimelineSourceMode {
|
||||
if timelineViewController === regularTimelineViewController {
|
||||
return .regular
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MainTimelineViewController" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="TimelineViewController" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="tableView" destination="opA-RM-DKR" id="Hnf-mE-gcq"/>
|
||||
<outlet property="view" destination="dbt-sN-FU2" id="96u-gC-hW0"/>
|
||||
|
@ -11,7 +11,7 @@ import RSCore
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
extension MainTimelineViewController {
|
||||
extension TimelineViewController {
|
||||
|
||||
func contextualMenuForClickedRows() -> NSMenu? {
|
||||
|
||||
@ -30,7 +30,7 @@ extension MainTimelineViewController {
|
||||
|
||||
// MARK: Contextual Menu Actions
|
||||
|
||||
extension MainTimelineViewController {
|
||||
extension TimelineViewController {
|
||||
|
||||
@objc func markArticlesReadFromContextualMenu(_ sender: Any?) {
|
||||
guard let articles = articles(from: sender) else { return }
|
||||
@ -107,7 +107,7 @@ extension MainTimelineViewController {
|
||||
}
|
||||
|
||||
|
||||
private extension MainTimelineViewController {
|
||||
private extension TimelineViewController {
|
||||
|
||||
func markArticles(_ articles: [Article], read: Bool) {
|
||||
markArticles(articles, statusKey: .read, flag: read)
|
||||
|
@ -13,9 +13,9 @@ import Account
|
||||
import os.log
|
||||
|
||||
protocol TimelineDelegate: AnyObject {
|
||||
func timelineSelectionDidChange(_: MainTimelineViewController, selectedArticles: [Article]?)
|
||||
func timelineRequestedFeedSelection(_: MainTimelineViewController, feed: Feed)
|
||||
func timelineInvalidatedRestorationState(_: MainTimelineViewController)
|
||||
func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
|
||||
func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed)
|
||||
func timelineInvalidatedRestorationState(_: TimelineViewController)
|
||||
}
|
||||
|
||||
enum TimelineShowFeedName {
|
||||
@ -24,7 +24,7 @@ enum TimelineShowFeedName {
|
||||
case feed
|
||||
}
|
||||
|
||||
final class MainTimelineViewController: NSViewController, UndoableCommandRunner, UnreadCountProvider {
|
||||
final class TimelineViewController: NSViewController, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
@IBOutlet var tableView: TimelineTableView!
|
||||
|
||||
@ -211,7 +211,7 @@ final class MainTimelineViewController: NSViewController, UndoableCommandRunner,
|
||||
|
||||
if !didRegisterForNotifications {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .feedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
|
||||
@ -723,7 +723,7 @@ final class MainTimelineViewController: NSViewController, UndoableCommandRunner,
|
||||
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date())
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: Constants.prototypeText, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status)
|
||||
|
||||
let prototypeCellData = MainTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil)
|
||||
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil)
|
||||
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
|
||||
return height
|
||||
}
|
||||
@ -759,7 +759,7 @@ final class MainTimelineViewController: NSViewController, UndoableCommandRunner,
|
||||
|
||||
// MARK: - NSMenuDelegate
|
||||
|
||||
extension MainTimelineViewController: NSMenuDelegate {
|
||||
extension TimelineViewController: NSMenuDelegate {
|
||||
|
||||
public func menuNeedsUpdate(_ menu: NSMenu) {
|
||||
menu.removeAllItems()
|
||||
@ -772,7 +772,7 @@ extension MainTimelineViewController: NSMenuDelegate {
|
||||
|
||||
// MARK: - NSUserInterfaceValidations
|
||||
|
||||
extension MainTimelineViewController: NSUserInterfaceValidations {
|
||||
extension TimelineViewController: NSUserInterfaceValidations {
|
||||
|
||||
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
if item.action == #selector(openArticleInBrowser(_:)) {
|
||||
@ -794,7 +794,7 @@ extension MainTimelineViewController: NSUserInterfaceValidations {
|
||||
|
||||
// MARK: - NSTableViewDataSource
|
||||
|
||||
extension MainTimelineViewController: NSTableViewDataSource {
|
||||
extension TimelineViewController: NSTableViewDataSource {
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
return articles.count
|
||||
}
|
||||
@ -813,15 +813,15 @@ extension MainTimelineViewController: NSTableViewDataSource {
|
||||
|
||||
// MARK: - NSTableViewDelegate
|
||||
|
||||
extension MainTimelineViewController: NSTableViewDelegate {
|
||||
extension TimelineViewController: NSTableViewDelegate {
|
||||
private static let rowViewIdentifier = NSUserInterfaceItemIdentifier(rawValue: "timelineRow")
|
||||
|
||||
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
|
||||
if let rowView: TimelineTableRowView = tableView.makeView(withIdentifier: MainTimelineViewController.rowViewIdentifier, owner: nil) as? TimelineTableRowView {
|
||||
if let rowView: TimelineTableRowView = tableView.makeView(withIdentifier: TimelineViewController.rowViewIdentifier, owner: nil) as? TimelineTableRowView {
|
||||
return rowView
|
||||
}
|
||||
let rowView = TimelineTableRowView()
|
||||
rowView.identifier = MainTimelineViewController.rowViewIdentifier
|
||||
rowView.identifier = TimelineViewController.rowViewIdentifier
|
||||
return rowView
|
||||
}
|
||||
|
||||
@ -839,13 +839,13 @@ extension MainTimelineViewController: NSTableViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if let cell = tableView.makeView(withIdentifier: MainTimelineViewController.timelineCellIdentifier, owner: nil) as? TimelineTableCellView {
|
||||
if let cell = tableView.makeView(withIdentifier: TimelineViewController.timelineCellIdentifier, owner: nil) as? TimelineTableCellView {
|
||||
configure(cell)
|
||||
return cell
|
||||
}
|
||||
|
||||
let cell = TimelineTableCellView()
|
||||
cell.identifier = MainTimelineViewController.timelineCellIdentifier
|
||||
cell.identifier = TimelineViewController.timelineCellIdentifier
|
||||
configure(cell)
|
||||
return cell
|
||||
}
|
||||
@ -876,7 +876,7 @@ extension MainTimelineViewController: NSTableViewDelegate {
|
||||
private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) {
|
||||
cell.objectValue = article
|
||||
let iconImage = article.iconImage()
|
||||
cell.cellData = MainTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
|
||||
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
|
||||
}
|
||||
|
||||
private func iconFor(_ article: Article) -> IconImage? {
|
||||
@ -887,12 +887,12 @@ extension MainTimelineViewController: NSTableViewDelegate {
|
||||
}
|
||||
|
||||
private func avatarForAuthor(_ author: Author) -> IconImage? {
|
||||
return appDelegate.authorAvatarDownloader.image(for: author)
|
||||
return AuthorAvatarDownloader.shared.image(for: author)
|
||||
}
|
||||
|
||||
private func makeTimelineCellEmpty(_ cell: TimelineTableCellView) {
|
||||
cell.objectValue = nil
|
||||
cell.cellData = MainTimelineCellData()
|
||||
cell.cellData = TimelineCellData()
|
||||
}
|
||||
|
||||
private func toggleArticleRead(_ article: Article) {
|
||||
@ -943,7 +943,7 @@ extension MainTimelineViewController: NSTableViewDelegate {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private extension MainTimelineViewController {
|
||||
private extension TimelineViewController {
|
||||
|
||||
func fetchAndReplacePreservingSelection() {
|
||||
if let article = oneSelectedArticle, let account = article.account {
|
||||
@ -1187,7 +1187,7 @@ private extension MainTimelineViewController {
|
||||
}
|
||||
|
||||
func queueFetchAndMergeArticles() {
|
||||
MainTimelineViewController.fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles))
|
||||
TimelineViewController.fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles))
|
||||
}
|
||||
|
||||
func representedObjectArraysAreEqual(_ objects1: [AnyObject]?, _ objects2: [AnyObject]?) -> Bool {
|
@ -920,7 +920,7 @@
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 1240;
|
||||
LastUpgradeCheck = 1610;
|
||||
LastUpgradeCheck = 1620;
|
||||
ORGANIZATIONNAME = "Ranchero Software";
|
||||
TargetAttributes = {
|
||||
176813F22564BB2C00D98635 = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.8">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
67
RSCore/Sources/RSCore/AppConfig.swift
Normal file
67
RSCore/Sources/RSCore/AppConfig.swift
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// AppConfig.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 6/26/24.
|
||||
// Copyright © 2024 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class AppConfig {
|
||||
|
||||
public static let appName: String = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String)
|
||||
|
||||
public static let cacheFolder: URL = {
|
||||
|
||||
let folderURL: URL
|
||||
|
||||
if let userCacheFolder = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) {
|
||||
folderURL = userCacheFolder
|
||||
} else {
|
||||
let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String)
|
||||
let tempFolder = (NSTemporaryDirectory() as NSString).appendingPathComponent(bundleIdentifier)
|
||||
folderURL = URL(fileURLWithPath: tempFolder, isDirectory: true)
|
||||
createFolderIfNecessary(folderURL)
|
||||
}
|
||||
|
||||
return folderURL
|
||||
}()
|
||||
|
||||
/// Returns URL to subfolder in cache folder (creating the folder if it doesn’t exist)
|
||||
public static func cacheSubfolder(named name: String) -> URL {
|
||||
ensureSubfolder(named: name, folderURL: cacheFolder)
|
||||
}
|
||||
|
||||
public static let dataFolder: URL = {
|
||||
|
||||
#if os(macOS)
|
||||
var dataFolder = try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
dataFolder = dataFolder.appendingPathComponent(appName)
|
||||
#elseif os(iOS)
|
||||
var dataFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
#endif
|
||||
|
||||
createFolderIfNecessary(dataFolder)
|
||||
return dataFolder
|
||||
}()
|
||||
|
||||
/// Returns URL to subfolder in data folder (creating the folder if it doesn’t exist)
|
||||
public static func dataSubfolder(named name: String) -> URL {
|
||||
ensureSubfolder(named: name, folderURL: dataFolder)
|
||||
}
|
||||
|
||||
public static func ensureSubfolder(named name: String, folderURL: URL) -> URL {
|
||||
|
||||
let folder = folderURL.appendingPathComponent(name, isDirectory: true)
|
||||
createFolderIfNecessary(folder)
|
||||
return folder
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppConfig {
|
||||
|
||||
static func createFolderIfNecessary(_ folderURL: URL) {
|
||||
try! FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -12,7 +12,7 @@ import Articles
|
||||
import Account
|
||||
import RSCore
|
||||
import RSWeb
|
||||
import RSParser
|
||||
import Parser
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension Notification.Name {
|
||||
@ -305,18 +305,18 @@ private extension FaviconDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
private extension RSHTMLMetadata {
|
||||
private extension HTMLMetadata {
|
||||
|
||||
func usableFaviconURLs() -> [String]? {
|
||||
|
||||
favicons.compactMap { favicon in
|
||||
favicons?.compactMap { favicon in
|
||||
shouldAllowFavicon(favicon) ? favicon.urlString : nil
|
||||
}
|
||||
}
|
||||
|
||||
static let ignoredTypes = [UTType.svg]
|
||||
|
||||
private func shouldAllowFavicon(_ favicon: RSHTMLMetadataFavicon) -> Bool {
|
||||
private func shouldAllowFavicon(_ favicon: HTMLMetadataFavicon) -> Bool {
|
||||
|
||||
// Check mime type.
|
||||
if let mimeType = favicon.type, let utType = UTType(mimeType: mimeType) {
|
||||
|
@ -1,67 +0,0 @@
|
||||
//
|
||||
// FaviconURLFinder.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 11/20/17.
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreServices
|
||||
import Parser
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
// The favicon URLs may be specified in the head section of the home page.
|
||||
|
||||
struct FaviconURLFinder {
|
||||
|
||||
/// Finds favicon URLs in a web page.
|
||||
/// - Parameters:
|
||||
/// - homePageURL: The page to search.
|
||||
/// - completion: A closure called when the links have been found.
|
||||
/// - urls: An array of favicon URLs as strings.
|
||||
static func findFaviconURLs(with homePageURL: String, _ completion: @escaping ([String]?) -> Void) {
|
||||
|
||||
guard let _ = URL(string: homePageURL) else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// If the favicon has an explicit type, check that for an ignored type; otherwise, check the file extension.
|
||||
HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (htmlMetadata) in
|
||||
|
||||
guard let favicons = htmlMetadata?.favicons else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
let faviconURLs = favicons.compactMap {
|
||||
shouldAllowFavicon($0) ? $0.urlString : nil
|
||||
}
|
||||
|
||||
completion(faviconURLs)
|
||||
}
|
||||
}
|
||||
|
||||
private static let ignoredTypes = [UTType.svg]
|
||||
|
||||
private static func shouldAllowFavicon(_ favicon: HTMLMetadataFavicon) -> Bool {
|
||||
|
||||
// Check mime type.
|
||||
if let mimeType = favicon.type, let utType = UTType(mimeType: mimeType) {
|
||||
if Self.ignoredTypes.contains(utType) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check file extension.
|
||||
if let urlString = favicon.urlString, let url = URL(string: urlString), let utType = UTType(filenameExtension: url.pathExtension) {
|
||||
if Self.ignoredTypes.contains(utType) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
//
|
||||
// HTMLMetadataDownloader.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 11/26/17.
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSWeb
|
||||
import Parser
|
||||
|
||||
struct HTMLMetadataDownloader {
|
||||
|
||||
static let serialDispatchQueue = DispatchQueue(label: "HTMLMetadataDownloader")
|
||||
|
||||
static func downloadMetadata(for url: String, _ completion: @escaping (HTMLMetadata?) -> Void) {
|
||||
guard let actualURL = URL(string: url) else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
downloadUsingCache(actualURL) { (data, response, error) in
|
||||
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
|
||||
let urlToUse = response.url ?? actualURL
|
||||
let parserData = ParserData(url: urlToUse.absoluteString, data: data)
|
||||
parseMetadata(with: parserData, completion)
|
||||
return
|
||||
}
|
||||
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseMetadata(with parserData: ParserData, _ completion: @escaping (HTMLMetadata?) -> Void) {
|
||||
serialDispatchQueue.async {
|
||||
let htmlMetadata = HTMLMetadataParser.metadata(with: parserData)
|
||||
DispatchQueue.main.async {
|
||||
completion(htmlMetadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -24,8 +24,9 @@ public final class FeedIconDownloader {
|
||||
|
||||
private let imageDownloader = ImageDownloader.shared
|
||||
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
|
||||
private let imageDownloader: ImageDownloader
|
||||
private var homePagesWithNoIconURL = Set<String>()
|
||||
private var cache = [Feed: IconImage]()
|
||||
private var waitingForFeedURLs = [String: Feed]()
|
||||
|
||||
private var feedURLToIconURLCache = [String: String]()
|
||||
private var feedURLToIconURLCachePath: URL
|
||||
|
@ -1,107 +0,0 @@
|
||||
//
|
||||
// SmartFeedSummaryWidget.swift
|
||||
// NetNewsWire Widget Extension
|
||||
//
|
||||
// Created by Stuart Breckenridge on 18/11/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct SmartFeedSummaryWidgetView: View {
|
||||
|
||||
@Environment(\.widgetFamily) var family: WidgetFamily
|
||||
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
smallWidget
|
||||
.widgetURL(WidgetDeepLink.icon.url)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var smallWidget: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer()
|
||||
Link(destination: WidgetDeepLink.today.url, label: {
|
||||
HStack {
|
||||
todayImage
|
||||
VStack(alignment: .leading, spacing: nil, content: {
|
||||
Text(formattedCount(entry.widgetData.currentTodayCount)).font(Font.system(.caption, design: .rounded)).bold()
|
||||
Text(L10n.today).bold().font(.caption).textCase(.uppercase)
|
||||
}).foregroundColor(.white)
|
||||
Spacer()
|
||||
}
|
||||
})
|
||||
|
||||
Link(destination: WidgetDeepLink.unread.url, label: {
|
||||
HStack {
|
||||
unreadImage
|
||||
VStack(alignment: .leading, spacing: nil, content: {
|
||||
Text(formattedCount(entry.widgetData.currentUnreadCount)).font(Font.system(.caption, design: .rounded)).bold()
|
||||
Text(L10n.unread).bold().font(.caption).textCase(.uppercase)
|
||||
}).foregroundColor(.white)
|
||||
Spacer()
|
||||
}
|
||||
})
|
||||
|
||||
Link(destination: WidgetDeepLink.starred.url, label: {
|
||||
HStack {
|
||||
starredImage
|
||||
VStack(alignment: .leading, spacing: nil, content: {
|
||||
Text(formattedCount(entry.widgetData.currentStarredCount)).font(Font.system(.caption, design: .rounded)).bold()
|
||||
Text(L10n.starred).bold().font(.caption).textCase(.uppercase)
|
||||
}).foregroundColor(.white)
|
||||
Spacer()
|
||||
}
|
||||
})
|
||||
Spacer()
|
||||
}.padding()
|
||||
}
|
||||
|
||||
func formattedCount(_ count: Int) -> String {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.numberStyle = .decimal
|
||||
return formatter.string(from: NSNumber(value: count))!
|
||||
}
|
||||
|
||||
var unreadImage: some View {
|
||||
Image(systemName: "largecircle.fill.circle")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
var nnwImage: some View {
|
||||
Image("CornerIcon")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.cornerRadius(4)
|
||||
}
|
||||
|
||||
var starredImage: some View {
|
||||
Image(systemName: "star.fill")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
var todayImage: some View {
|
||||
Image(systemName: "sun.max.fill")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct SmartFeedSummaryWidgetView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
SmartFeedSummaryWidgetView(entry: Provider.Entry.init(date: Date(), widgetData: WidgetDataDecoder.sampleData()))
|
||||
}
|
||||
}
|
19
Widget/Widget Views/WidgetLayout.swift
Normal file
19
Widget/Widget Views/WidgetLayout.swift
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// WidgetLayout.swift
|
||||
// NetNewsWire iOS Widget Extension
|
||||
//
|
||||
// Created by Brent Simmons on 12/26/24.
|
||||
// Copyright © 2024 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct WidgetLayout {
|
||||
|
||||
static let titleImageSize = CGFloat(20)
|
||||
static let titleImagePaddingRight = CGFloat(6)
|
||||
static let leftSideWidth = titleImageSize + titleImagePaddingRight
|
||||
static let feedIconSize = CGFloat(24)
|
||||
static let articleItemViewPaddingTop = CGFloat(8)
|
||||
static let articleItemViewPaddingBottom = CGFloat(4)
|
||||
}
|
@ -68,7 +68,7 @@ class WebViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .feedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(currentArticleThemeDidChangeNotification(_:)), name: .CurrentArticleThemeDidChangeNotification, object: nil)
|
||||
|
@ -112,9 +112,9 @@
|
||||
<!--Timeline-->
|
||||
<scene sceneID="fag-XH-avP">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="MainTimelineViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" clearsSelectionOnViewWillAppear="NO" id="Kyk-vK-QRX" customClass="MainTimelineViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController storyboardIdentifier="MainTimelineViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" clearsSelectionOnViewWillAppear="NO" id="Kyk-vK-QRX" customClass="TimelineViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="onDrag" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="mtv-Ik-FoJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="721"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<prototypes>
|
||||
@ -431,7 +431,7 @@
|
||||
<color red="0.031372549019607843" green="0.41568627450980394" blue="0.93333333333333335" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<systemColor name="separatorColor">
|
||||
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
|
@ -48,7 +48,7 @@ class FeedInspectorViewController: UITableViewController {
|
||||
homePageLabel.text = feed.homePageURL
|
||||
feedURLLabel.text = feed.url
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .feedIconDidBecomeAvailable, object: nil)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateNotificationSettings), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||
|
||||
|
@ -11,7 +11,7 @@ import RSCore
|
||||
import Account
|
||||
import Articles
|
||||
|
||||
class MainTimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
|
||||
private var numberOfTextLines = 0
|
||||
private var iconSize = IconSize.medium
|
||||
@ -552,7 +552,7 @@ class MainTimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date())
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: Constants.prototypeText, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status)
|
||||
|
||||
let prototypeCellData = MainTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize)
|
||||
let prototypeCellData = MainTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, numberOfLines: numberOfTextLines, iconSize: iconSize)
|
||||
|
||||
if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory {
|
||||
let layout = MainTimelineAccessibilityCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData)
|
||||
@ -568,7 +568,7 @@ class MainTimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
|
||||
// MARK: Searching
|
||||
|
||||
extension MainTimelineViewController: UISearchControllerDelegate {
|
||||
extension TimelineViewController: UISearchControllerDelegate {
|
||||
|
||||
func willPresentSearchController(_ searchController: UISearchController) {
|
||||
coordinator.beginSearching()
|
||||
@ -582,7 +582,7 @@ extension MainTimelineViewController: UISearchControllerDelegate {
|
||||
|
||||
}
|
||||
|
||||
extension MainTimelineViewController: UISearchResultsUpdating {
|
||||
extension TimelineViewController: UISearchResultsUpdating {
|
||||
|
||||
func updateSearchResults(for searchController: UISearchController) {
|
||||
let searchScope = SearchScope(rawValue: searchController.searchBar.selectedScopeButtonIndex)!
|
||||
@ -591,7 +591,7 @@ extension MainTimelineViewController: UISearchResultsUpdating {
|
||||
|
||||
}
|
||||
|
||||
extension MainTimelineViewController: UISearchBarDelegate {
|
||||
extension TimelineViewController: UISearchBarDelegate {
|
||||
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
|
||||
let searchScope = SearchScope(rawValue: selectedScope)!
|
||||
coordinator.searchArticles(searchBar.text!, searchScope)
|
||||
@ -600,7 +600,7 @@ extension MainTimelineViewController: UISearchBarDelegate {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private extension MainTimelineViewController {
|
||||
private extension TimelineViewController {
|
||||
|
||||
func configureToolbar() {
|
||||
guard !(splitViewController?.isCollapsed ?? true) else {
|
@ -53,7 +53,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
private var rootSplitViewController: RootSplitViewController!
|
||||
|
||||
private var mainFeedViewController: MainFeedViewController!
|
||||
private var mainTimelineViewController: MainTimelineViewController?
|
||||
private var mainTimelineViewController: TimelineViewController?
|
||||
private var articleViewController: ArticleViewController?
|
||||
|
||||
private let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5)
|
||||
@ -284,7 +284,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
self.mainFeedViewController.coordinator = self
|
||||
self.mainFeedViewController?.navigationController?.delegate = self
|
||||
|
||||
self.mainTimelineViewController = rootSplitViewController.viewController(for: .supplementary) as? MainTimelineViewController
|
||||
self.mainTimelineViewController = rootSplitViewController.viewController(for: .supplementary) as? TimelineViewController
|
||||
self.mainTimelineViewController?.coordinator = self
|
||||
self.mainTimelineViewController?.navigationController?.delegate = self
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user