Merge branch 'swiftui' into swiftui
This commit is contained in:
commit
cec866cf27
@ -104,6 +104,16 @@ struct AppAssets {
|
|||||||
#endif
|
#endif
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
static var timelineStarred: Image = {
|
||||||
|
return Image(systemName: "star.fill")
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
static var timelineUnread: Image = {
|
||||||
|
return Image(systemName: "circle.fill")
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
static var todayFeedImage: IconImage = {
|
static var todayFeedImage: IconImage = {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
return IconImage(NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)!)
|
return IconImage(NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)!)
|
||||||
|
59
Multiplatform/Shared/Images/ArticleIconImageLoader.swift
Normal file
59
Multiplatform/Shared/Images/ArticleIconImageLoader.swift
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// ArticleIconImageLoader.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 7/1/20.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Account
|
||||||
|
import Articles
|
||||||
|
|
||||||
|
final class ArticleIconImageLoader: ObservableObject {
|
||||||
|
|
||||||
|
private var article: Article?
|
||||||
|
|
||||||
|
@Published var image: IconImage?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImage(for article: Article) {
|
||||||
|
guard image == nil else { return }
|
||||||
|
self.article = article
|
||||||
|
image = article.iconImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ArticleIconImageLoader {
|
||||||
|
|
||||||
|
@objc func faviconDidBecomeAvailable(_ note: Notification) {
|
||||||
|
guard let article = article else { return }
|
||||||
|
image = article.iconImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||||
|
guard let article = article, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, noteFeed == article.webFeed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
image = article.iconImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func avatarDidBecomeAvailable(_ note: Notification) {
|
||||||
|
guard let article = article, let authors = article.authors, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for author in authors {
|
||||||
|
if author.avatarURL == avatarURL {
|
||||||
|
image = article.iconImage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// FeedImageLoader.swift
|
// FeedIconImageLoader.swift
|
||||||
// NetNewsWire
|
// NetNewsWire
|
||||||
//
|
//
|
||||||
// Created by Maurice Parker on 6/29/20.
|
// Created by Maurice Parker on 6/29/20.
|
||||||
@ -9,7 +9,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Account
|
import Account
|
||||||
|
|
||||||
final class FeedImageLoader: ObservableObject {
|
final class FeedIconImageLoader: ObservableObject {
|
||||||
|
|
||||||
private var feed: Feed?
|
private var feed: Feed?
|
||||||
|
|
||||||
@ -21,8 +21,27 @@ final class FeedImageLoader: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadImage(for feed: Feed) {
|
func loadImage(for feed: Feed) {
|
||||||
|
guard image == nil else { return }
|
||||||
self.feed = feed
|
self.feed = feed
|
||||||
|
fetchImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension FeedIconImageLoader {
|
||||||
|
|
||||||
|
@objc func faviconDidBecomeAvailable(_ note: Notification) {
|
||||||
|
fetchImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||||
|
guard let feed = feed as? WebFeed, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, feed == noteFeed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetchImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchImage() {
|
||||||
if let webFeed = feed as? WebFeed {
|
if let webFeed = feed as? WebFeed {
|
||||||
if let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed) {
|
if let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed) {
|
||||||
image = feedIconImage
|
image = feedIconImage
|
||||||
@ -40,19 +59,3 @@ final class FeedImageLoader: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension FeedImageLoader {
|
|
||||||
|
|
||||||
@objc func faviconDidBecomeAvailable(_ note: Notification) {
|
|
||||||
guard let feed = feed else { return }
|
|
||||||
loadImage(for: feed)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
|
||||||
guard let feed = feed as? WebFeed, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, feed == noteFeed else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
loadImage(for: feed)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -13,20 +13,10 @@ struct IconImageView: View {
|
|||||||
var iconImage: IconImage
|
var iconImage: IconImage
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(macOS)
|
return Image(rsImage: iconImage.image)
|
||||||
return Image(nsImage: iconImage.image)
|
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
|
||||||
.cornerRadius(4)
|
.cornerRadius(4)
|
||||||
#endif
|
|
||||||
#if os(iOS)
|
|
||||||
return Image(uiImage: iconImage.image)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
|
||||||
.cornerRadius(4)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
58
Multiplatform/Shared/Previews/PreviewArticles.swift
Normal file
58
Multiplatform/Shared/Previews/PreviewArticles.swift
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// PreviewArticles.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 7/1/20.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Articles
|
||||||
|
|
||||||
|
enum PreviewArticles {
|
||||||
|
|
||||||
|
static var basicUnread: Article {
|
||||||
|
return makeBasicArticle(read: false, starred: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var basicRead: Article {
|
||||||
|
return makeBasicArticle(read: true, starred: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var basicStarred: Article {
|
||||||
|
return makeBasicArticle(read: false, starred: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension PreviewArticles {
|
||||||
|
|
||||||
|
static var shortTitle: String {
|
||||||
|
return "Short article title"
|
||||||
|
}
|
||||||
|
|
||||||
|
static var shortSummary: String {
|
||||||
|
return "Summary of article to be shown after title."
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeBasicArticle(read: Bool, starred: Bool) -> Article {
|
||||||
|
let articleID = "prototype"
|
||||||
|
let status = ArticleStatus(articleID: articleID, read: read, starred: starred, dateArrived: Date())
|
||||||
|
return Article(accountID: articleID,
|
||||||
|
articleID: articleID,
|
||||||
|
webFeedID: articleID,
|
||||||
|
uniqueID: articleID,
|
||||||
|
title: shortTitle,
|
||||||
|
contentHTML: nil,
|
||||||
|
contentText: nil,
|
||||||
|
url: nil,
|
||||||
|
externalURL: nil,
|
||||||
|
summary: shortSummary,
|
||||||
|
imageURL: nil,
|
||||||
|
datePublished: Date(),
|
||||||
|
dateModified: nil,
|
||||||
|
authors: nil,
|
||||||
|
status: status)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -20,10 +20,6 @@ final class SceneModel: ObservableObject {
|
|||||||
|
|
||||||
extension SceneModel: SidebarModelDelegate {
|
extension SceneModel: SidebarModelDelegate {
|
||||||
|
|
||||||
func sidebarSelectionDidChange(_: SidebarModel, feeds: [Feed]?) {
|
|
||||||
print("**** sidebar selection changed ***")
|
|
||||||
}
|
|
||||||
|
|
||||||
func unreadCount(for feed: Feed) -> Int {
|
func unreadCount(for feed: Feed) -> Int {
|
||||||
// TODO: Get the count from the timeline if Feed is the current timeline
|
// TODO: Get the count from the timeline if Feed is the current timeline
|
||||||
return feed.unreadCount
|
return feed.unreadCount
|
||||||
|
@ -29,11 +29,7 @@ final class SidebarExpandedContainers: ObservableObject {
|
|||||||
|
|
||||||
subscript(_ containerID: ContainerIdentifier) -> Bool {
|
subscript(_ containerID: ContainerIdentifier) -> Bool {
|
||||||
get {
|
get {
|
||||||
if expandedTable.contains(containerID) {
|
return expandedTable.contains(containerID)
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
if newValue {
|
if newValue {
|
||||||
|
@ -11,13 +11,14 @@ import Account
|
|||||||
|
|
||||||
struct SidebarItemView: View {
|
struct SidebarItemView: View {
|
||||||
|
|
||||||
@StateObject var feedImageLoader = FeedImageLoader()
|
@StateObject var feedIconImageLoader = FeedIconImageLoader()
|
||||||
var sidebarItem: SidebarItem
|
var sidebarItem: SidebarItem
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
if let image = feedImageLoader.image {
|
if let image = feedIconImageLoader.image {
|
||||||
IconImageView(iconImage: image)
|
IconImageView(iconImage: image)
|
||||||
|
.frame(width: 20, height: 20, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
||||||
}
|
}
|
||||||
Text(verbatim: sidebarItem.nameForDisplay)
|
Text(verbatim: sidebarItem.nameForDisplay)
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -27,7 +28,7 @@ struct SidebarItemView: View {
|
|||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if let feed = sidebarItem.feed {
|
if let feed = sidebarItem.feed {
|
||||||
feedImageLoader.loadImage(for: feed)
|
feedIconImageLoader.loadImage(for: feed)
|
||||||
}
|
}
|
||||||
}.contextMenu(menuItems: {
|
}.contextMenu(menuItems: {
|
||||||
menuItems
|
menuItems
|
||||||
|
@ -11,7 +11,6 @@ import RSCore
|
|||||||
import Account
|
import Account
|
||||||
|
|
||||||
protocol SidebarModelDelegate: class {
|
protocol SidebarModelDelegate: class {
|
||||||
func sidebarSelectionDidChange(_: SidebarModel, feeds: [Feed]?)
|
|
||||||
func unreadCount(for: Feed) -> Int
|
func unreadCount(for: Feed) -> Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,6 @@ struct SidebarView: View {
|
|||||||
@StateObject private var expandedContainers = SidebarExpandedContainers()
|
@StateObject private var expandedContainers = SidebarExpandedContainers()
|
||||||
@EnvironmentObject private var sidebarModel: SidebarModel
|
@EnvironmentObject private var sidebarModel: SidebarModel
|
||||||
|
|
||||||
// @State private var selected = Set<FeedIdentifier>()
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List() {
|
List() {
|
||||||
ForEach(sidebarModel.sidebarItems) { sidebarItem in
|
ForEach(sidebarModel.sidebarItems) { sidebarItem in
|
||||||
@ -27,13 +25,19 @@ struct SidebarView: View {
|
|||||||
if let containerID = sidebarItem.containerID {
|
if let containerID = sidebarItem.containerID {
|
||||||
DisclosureGroup(isExpanded: $expandedContainers[containerID]) {
|
DisclosureGroup(isExpanded: $expandedContainers[containerID]) {
|
||||||
ForEach(sidebarItem.children) { sidebarItem in
|
ForEach(sidebarItem.children) { sidebarItem in
|
||||||
SidebarItemView(sidebarItem: sidebarItem)
|
NavigationLink(destination: (TimelineContainerView(feed: sidebarItem.feed))) {
|
||||||
|
SidebarItemView(sidebarItem: sidebarItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
SidebarItemView(sidebarItem: sidebarItem)
|
NavigationLink(destination: (TimelineContainerView(feed: sidebarItem.feed))) {
|
||||||
|
SidebarItemView(sidebarItem: sidebarItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SidebarItemView(sidebarItem: sidebarItem)
|
NavigationLink(destination: (TimelineContainerView(feed: sidebarItem.feed))) {
|
||||||
|
SidebarItemView(sidebarItem: sidebarItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Image-Extensions.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 7/1/20.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import RSCore
|
||||||
|
|
||||||
|
extension Image {
|
||||||
|
|
||||||
|
init(rsImage: RSImage) {
|
||||||
|
#if os(macOS)
|
||||||
|
self = Image(nsImage: rsImage)
|
||||||
|
#endif
|
||||||
|
#if os(iOS)
|
||||||
|
self = Image(uiImage: rsImage)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,28 +7,26 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Account
|
||||||
|
|
||||||
struct TimelineContainerView: View {
|
struct TimelineContainerView: View {
|
||||||
|
|
||||||
@EnvironmentObject private var sceneModel: SceneModel
|
@EnvironmentObject private var sceneModel: SceneModel
|
||||||
@StateObject private var timelineModel = TimelineModel()
|
@StateObject private var timelineModel = TimelineModel()
|
||||||
|
var feed: Feed? = nil
|
||||||
|
|
||||||
@ViewBuilder var body: some View {
|
@ViewBuilder var body: some View {
|
||||||
TimelineView()
|
if let feed = feed {
|
||||||
.environmentObject(timelineModel)
|
TimelineView()
|
||||||
.listStyle(SidebarListStyle())
|
.environmentObject(timelineModel)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
sceneModel.timelineModel = timelineModel
|
sceneModel.timelineModel = timelineModel
|
||||||
timelineModel.delegate = sceneModel
|
timelineModel.delegate = sceneModel
|
||||||
timelineModel.rebuildTimelineItems()
|
timelineModel.rebuildTimelineItems(feed)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimelineContainerView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
TimelineContainerView()
|
|
||||||
.environmentObject(SceneModel())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -9,9 +9,36 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Articles
|
import Articles
|
||||||
|
|
||||||
|
enum TimelineItemStatus {
|
||||||
|
case showStar
|
||||||
|
case showUnread
|
||||||
|
case showNone
|
||||||
|
}
|
||||||
|
|
||||||
struct TimelineItem: Identifiable {
|
struct TimelineItem: Identifiable {
|
||||||
|
|
||||||
var id: String
|
var article: Article
|
||||||
|
|
||||||
|
var id: String {
|
||||||
|
return article.articleID
|
||||||
|
}
|
||||||
|
|
||||||
|
var status: TimelineItemStatus {
|
||||||
|
if article.status.starred == true {
|
||||||
|
return .showStar
|
||||||
|
}
|
||||||
|
if article.status.read == false {
|
||||||
|
return .showUnread
|
||||||
|
}
|
||||||
|
return .showNone
|
||||||
|
}
|
||||||
|
|
||||||
|
var byline: String {
|
||||||
|
return article.webFeed?.nameForDisplay ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var dateTimeString: String {
|
||||||
|
return ArticleStringFormatter.dateString(article.logicalDatePublished)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
43
Multiplatform/Shared/Timeline/TimelineItemStatusView.swift
Normal file
43
Multiplatform/Shared/Timeline/TimelineItemStatusView.swift
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// TimelineItemStatusView.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 7/1/20.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct TimelineItemStatusView: View {
|
||||||
|
|
||||||
|
var status: TimelineItemStatus
|
||||||
|
|
||||||
|
@ViewBuilder var statusView: some View {
|
||||||
|
switch status {
|
||||||
|
case .showUnread:
|
||||||
|
AppAssets.timelineUnread
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 8, height: 8, alignment: .center)
|
||||||
|
.padding(.all, 2)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
case .showStar:
|
||||||
|
AppAssets.timelineStarred
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 10, height: 10, alignment: .center)
|
||||||
|
.foregroundColor(.yellow)
|
||||||
|
case .showNone:
|
||||||
|
AppAssets.timelineUnread
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 8, height: 8, alignment: .center)
|
||||||
|
.padding(.all, 2)
|
||||||
|
.opacity(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
statusView
|
||||||
|
.padding(.top, 4)
|
||||||
|
.padding(.leading, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
65
Multiplatform/Shared/Timeline/TimelineItemView.swift
Normal file
65
Multiplatform/Shared/Timeline/TimelineItemView.swift
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// TimelineItemView.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 7/1/20.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct TimelineItemView: View {
|
||||||
|
|
||||||
|
@StateObject var articleIconImageLoader = ArticleIconImageLoader()
|
||||||
|
var timelineItem: TimelineItem
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
TimelineItemStatusView(status: timelineItem.status)
|
||||||
|
if let image = articleIconImageLoader.image {
|
||||||
|
IconImageView(iconImage: image)
|
||||||
|
.frame(width: AppDefaults.timelineIconSize.size.width, height: AppDefaults.timelineIconSize.size.height, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
Text(verbatim: timelineItem.article.title ?? "N/A")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.lineLimit(AppDefaults.timelineNumberOfLines)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(.trailing, 4)
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Text(verbatim: timelineItem.byline)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Spacer()
|
||||||
|
Text(verbatim: timelineItem.dateTimeString)
|
||||||
|
.lineLimit(1)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.trailing, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
articleIconImageLoader.loadImage(for: timelineItem.article)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimelineItemView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
Group {
|
||||||
|
TimelineItemView(timelineItem: TimelineItem(article: PreviewArticles.basicRead))
|
||||||
|
.frame(maxWidth: 250)
|
||||||
|
TimelineItemView(timelineItem: TimelineItem(article: PreviewArticles.basicUnread))
|
||||||
|
.frame(maxWidth: 250)
|
||||||
|
TimelineItemView(timelineItem: TimelineItem(article: PreviewArticles.basicStarred))
|
||||||
|
.frame(maxWidth: 250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
import Account
|
import Account
|
||||||
|
import Articles
|
||||||
|
|
||||||
protocol TimelineModelDelegate: class {
|
protocol TimelineModelDelegate: class {
|
||||||
func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed)
|
func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed)
|
||||||
@ -20,19 +21,103 @@ class TimelineModel: ObservableObject {
|
|||||||
|
|
||||||
@Published var timelineItems = [TimelineItem]()
|
@Published var timelineItems = [TimelineItem]()
|
||||||
|
|
||||||
|
private var feeds = [Feed]()
|
||||||
|
private var fetchSerialNumber = 0
|
||||||
|
private let fetchRequestQueue = FetchRequestQueue()
|
||||||
|
private var exceptionArticleFetcher: ArticleFetcher?
|
||||||
|
private var isReadFiltered = false
|
||||||
|
|
||||||
|
private var articles = [Article]()
|
||||||
|
|
||||||
|
private var sortDirection = AppDefaults.timelineSortDirection {
|
||||||
|
didSet {
|
||||||
|
if sortDirection != oldValue {
|
||||||
|
sortParametersDidChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var groupByFeed = AppDefaults.timelineGroupByFeed {
|
||||||
|
didSet {
|
||||||
|
if groupByFeed != oldValue {
|
||||||
|
sortParametersDidChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: API
|
// MARK: API
|
||||||
|
|
||||||
func rebuildTimelineItems() {
|
func rebuildTimelineItems(_ feed: Feed) {
|
||||||
|
feeds = [feed]
|
||||||
|
fetchAndReplaceArticlesAsync()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private extension TimelineModel {
|
private extension TimelineModel {
|
||||||
|
|
||||||
|
func sortParametersDidChange() {
|
||||||
|
performBlockAndRestoreSelection {
|
||||||
|
let unsortedArticles = Set(articles)
|
||||||
|
replaceArticles(with: unsortedArticles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func performBlockAndRestoreSelection(_ block: (() -> Void)) {
|
||||||
|
// let savedSelection = selectedArticleIDs()
|
||||||
|
block()
|
||||||
|
// restoreSelection(savedSelection)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Article Fetching
|
||||||
|
|
||||||
|
func fetchAndReplaceArticlesAsync() {
|
||||||
|
cancelPendingAsyncFetches()
|
||||||
|
|
||||||
|
var fetchers = feeds as [ArticleFetcher]
|
||||||
|
if let fetcher = exceptionArticleFetcher {
|
||||||
|
fetchers.append(fetcher)
|
||||||
|
exceptionArticleFetcher = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUnsortedArticlesAsync(for: fetchers) { [weak self] (articles) in
|
||||||
|
self?.replaceArticles(with: articles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelPendingAsyncFetches() {
|
||||||
|
fetchSerialNumber += 1
|
||||||
|
fetchRequestQueue.cancelAllRequests()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchUnsortedArticlesAsync(for representedObjects: [Any], completion: @escaping ArticleSetBlock) {
|
||||||
|
// The callback will *not* be called if the fetch is no longer relevant — that is,
|
||||||
|
// if it’s been superseded by a newer fetch, or the timeline was emptied, etc., it won’t get called.
|
||||||
|
precondition(Thread.isMainThread)
|
||||||
|
cancelPendingAsyncFetches()
|
||||||
|
let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: isReadFiltered ?? true, representedObjects: representedObjects) { [weak self] (articles, operation) in
|
||||||
|
precondition(Thread.isMainThread)
|
||||||
|
guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
completion(articles)
|
||||||
|
}
|
||||||
|
fetchRequestQueue.add(fetchOperation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceArticles(with unsortedArticles: Set<Article>) {
|
||||||
|
articles = Array(unsortedArticles).sortedByDate(sortDirection, groupByFeed: groupByFeed)
|
||||||
|
timelineItems = articles.map { TimelineItem(article: $0) }
|
||||||
|
|
||||||
|
// TODO: Update unread counts and other item done in didSet on AppKit
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Notifications
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,23 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TimelineView: View {
|
struct TimelineView: View {
|
||||||
var body: some View {
|
|
||||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
@EnvironmentObject private var timelineModel: TimelineModel
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimelineView_Previews: PreviewProvider {
|
var body: some View {
|
||||||
static var previews: some View {
|
ScrollView {
|
||||||
TimelineView()
|
LazyVStack() {
|
||||||
|
ForEach(timelineModel.timelineItems) { timelineItem in
|
||||||
|
TimelineItemView(timelineItem: timelineItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// var body: some View {
|
||||||
|
// List(timelineModel.timelineItems) { timelineItem in
|
||||||
|
// TimelineItemView(timelineItem: timelineItem)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,16 @@
|
|||||||
514A89A6244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */; };
|
514A89A6244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */; };
|
||||||
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; };
|
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; };
|
||||||
514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */; };
|
514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */; };
|
||||||
|
514E6BDA24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */; };
|
||||||
|
514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */; };
|
||||||
|
514E6BFF24AD255D00AC6F6E /* PreviewArticles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6BFE24AD255D00AC6F6E /* PreviewArticles.swift */; };
|
||||||
|
514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6BFE24AD255D00AC6F6E /* PreviewArticles.swift */; };
|
||||||
|
514E6C0224AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */; };
|
||||||
|
514E6C0324AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */; };
|
||||||
|
514E6C0624AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */; };
|
||||||
|
514E6C0724AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */; };
|
||||||
|
514E6C0924AD39AD00AC6F6E /* ArticleIconImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0824AD39AD00AC6F6E /* ArticleIconImageLoader.swift */; };
|
||||||
|
514E6C0A24AD39AD00AC6F6E /* ArticleIconImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0824AD39AD00AC6F6E /* ArticleIconImageLoader.swift */; };
|
||||||
5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; };
|
5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; };
|
||||||
51554C24228B71910055115A /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; };
|
51554C24228B71910055115A /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; };
|
||||||
51554C25228B71910055115A /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
51554C25228B71910055115A /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
@ -222,8 +232,8 @@
|
|||||||
51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAB24AA8CCA00541E64 /* UnreadCountView.swift */; };
|
51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAB24AA8CCA00541E64 /* UnreadCountView.swift */; };
|
||||||
51919FAF24AA8EFA00541E64 /* SidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */; };
|
51919FAF24AA8EFA00541E64 /* SidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */; };
|
||||||
51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */; };
|
51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */; };
|
||||||
51919FB324AAB97900541E64 /* FeedImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB224AAB97900541E64 /* FeedImageLoader.swift */; };
|
51919FB324AAB97900541E64 /* FeedIconImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB224AAB97900541E64 /* FeedIconImageLoader.swift */; };
|
||||||
51919FB424AAB97900541E64 /* FeedImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB224AAB97900541E64 /* FeedImageLoader.swift */; };
|
51919FB424AAB97900541E64 /* FeedIconImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB224AAB97900541E64 /* FeedIconImageLoader.swift */; };
|
||||||
51919FB624AABCA100541E64 /* IconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB524AABCA100541E64 /* IconImageView.swift */; };
|
51919FB624AABCA100541E64 /* IconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB524AABCA100541E64 /* IconImageView.swift */; };
|
||||||
51919FB724AABCA100541E64 /* IconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB524AABCA100541E64 /* IconImageView.swift */; };
|
51919FB724AABCA100541E64 /* IconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB524AABCA100541E64 /* IconImageView.swift */; };
|
||||||
51919FEE24AB85E400541E64 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FED24AB85E400541E64 /* TimelineContainerView.swift */; };
|
51919FEE24AB85E400541E64 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FED24AB85E400541E64 /* TimelineContainerView.swift */; };
|
||||||
@ -1781,6 +1791,11 @@
|
|||||||
514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AddTwitterFeedWindowController.swift; path = AddFeed/AddTwitterFeedWindowController.swift; sourceTree = "<group>"; };
|
514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AddTwitterFeedWindowController.swift; path = AddFeed/AddTwitterFeedWindowController.swift; sourceTree = "<group>"; };
|
||||||
514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = "<group>"; };
|
514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = "<group>"; };
|
||||||
514B7D1E23219F3C00BAC947 /* AddControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddControllerType.swift; sourceTree = "<group>"; };
|
514B7D1E23219F3C00BAC947 /* AddControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddControllerType.swift; sourceTree = "<group>"; };
|
||||||
|
514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemView.swift; sourceTree = "<group>"; };
|
||||||
|
514E6BFE24AD255D00AC6F6E /* PreviewArticles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewArticles.swift; sourceTree = "<group>"; };
|
||||||
|
514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemStatusView.swift; sourceTree = "<group>"; };
|
||||||
|
514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image-Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
514E6C0824AD39AD00AC6F6E /* ArticleIconImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconImageLoader.swift; sourceTree = "<group>"; };
|
||||||
51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SyncDatabase.xcodeproj; path = Frameworks/SyncDatabase/SyncDatabase.xcodeproj; sourceTree = SOURCE_ROOT; };
|
51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SyncDatabase.xcodeproj; path = Frameworks/SyncDatabase/SyncDatabase.xcodeproj; sourceTree = SOURCE_ROOT; };
|
||||||
515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = "<group>"; };
|
515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = "<group>"; };
|
||||||
515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider-Extensions.swift"; sourceTree = "<group>"; };
|
515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider-Extensions.swift"; sourceTree = "<group>"; };
|
||||||
@ -1827,7 +1842,7 @@
|
|||||||
51919FA524AA64B000541E64 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
|
51919FA524AA64B000541E64 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
|
||||||
51919FAB24AA8CCA00541E64 /* UnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadCountView.swift; sourceTree = "<group>"; };
|
51919FAB24AA8CCA00541E64 /* UnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadCountView.swift; sourceTree = "<group>"; };
|
||||||
51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItemView.swift; sourceTree = "<group>"; };
|
51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItemView.swift; sourceTree = "<group>"; };
|
||||||
51919FB224AAB97900541E64 /* FeedImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedImageLoader.swift; sourceTree = "<group>"; };
|
51919FB224AAB97900541E64 /* FeedIconImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconImageLoader.swift; sourceTree = "<group>"; };
|
||||||
51919FB524AABCA100541E64 /* IconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImageView.swift; sourceTree = "<group>"; };
|
51919FB524AABCA100541E64 /* IconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImageView.swift; sourceTree = "<group>"; };
|
||||||
51919FED24AB85E400541E64 /* TimelineContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineContainerView.swift; sourceTree = "<group>"; };
|
51919FED24AB85E400541E64 /* TimelineContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineContainerView.swift; sourceTree = "<group>"; };
|
||||||
51919FF024AB864A00541E64 /* TimelineModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineModel.swift; sourceTree = "<group>"; };
|
51919FF024AB864A00541E64 /* TimelineModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineModel.swift; sourceTree = "<group>"; };
|
||||||
@ -2521,6 +2536,22 @@
|
|||||||
path = OPML;
|
path = OPML;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
514E6BFD24AD252400AC6F6E /* Previews */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
514E6BFE24AD255D00AC6F6E /* PreviewArticles.swift */,
|
||||||
|
);
|
||||||
|
path = Previews;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
514E6C0424AD2B0400AC6F6E /* SwiftUI Extensions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */,
|
||||||
|
);
|
||||||
|
path = "SwiftUI Extensions";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
51554BFD228B6EB50055115A /* Products */ = {
|
51554BFD228B6EB50055115A /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -2611,7 +2642,8 @@
|
|||||||
51919FB124AAB95300541E64 /* Images */ = {
|
51919FB124AAB95300541E64 /* Images */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
51919FB224AAB97900541E64 /* FeedImageLoader.swift */,
|
514E6C0824AD39AD00AC6F6E /* ArticleIconImageLoader.swift */,
|
||||||
|
51919FB224AAB97900541E64 /* FeedIconImageLoader.swift */,
|
||||||
51919FB524AABCA100541E64 /* IconImageView.swift */,
|
51919FB524AABCA100541E64 /* IconImageView.swift */,
|
||||||
);
|
);
|
||||||
path = Images;
|
path = Images;
|
||||||
@ -2622,8 +2654,10 @@
|
|||||||
children = (
|
children = (
|
||||||
51919FED24AB85E400541E64 /* TimelineContainerView.swift */,
|
51919FED24AB85E400541E64 /* TimelineContainerView.swift */,
|
||||||
51919FF324AB869C00541E64 /* TimelineItem.swift */,
|
51919FF324AB869C00541E64 /* TimelineItem.swift */,
|
||||||
|
514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */,
|
||||||
51919FF024AB864A00541E64 /* TimelineModel.swift */,
|
51919FF024AB864A00541E64 /* TimelineModel.swift */,
|
||||||
51919FF624AB8B7700541E64 /* TimelineView.swift */,
|
51919FF624AB8B7700541E64 /* TimelineView.swift */,
|
||||||
|
514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */,
|
||||||
);
|
);
|
||||||
path = Timeline;
|
path = Timeline;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2696,7 +2730,9 @@
|
|||||||
51E49A0224A91FF600B667CB /* SceneNavigationView.swift */,
|
51E49A0224A91FF600B667CB /* SceneNavigationView.swift */,
|
||||||
51C0513824A77DF800194D5E /* Assets.xcassets */,
|
51C0513824A77DF800194D5E /* Assets.xcassets */,
|
||||||
51919FB124AAB95300541E64 /* Images */,
|
51919FB124AAB95300541E64 /* Images */,
|
||||||
|
514E6BFD24AD252400AC6F6E /* Previews */,
|
||||||
51E499FB24A9135A00B667CB /* Sidebar */,
|
51E499FB24A9135A00B667CB /* Sidebar */,
|
||||||
|
514E6C0424AD2B0400AC6F6E /* SwiftUI Extensions */,
|
||||||
51919FCB24AB855000541E64 /* Timeline */,
|
51919FCB24AB855000541E64 /* Timeline */,
|
||||||
);
|
);
|
||||||
path = Shared;
|
path = Shared;
|
||||||
@ -4723,9 +4759,12 @@
|
|||||||
51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */,
|
51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */,
|
||||||
51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */,
|
51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */,
|
||||||
51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */,
|
51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */,
|
||||||
|
514E6C0924AD39AD00AC6F6E /* ArticleIconImageLoader.swift in Sources */,
|
||||||
51919FAF24AA8EFA00541E64 /* SidebarItemView.swift in Sources */,
|
51919FAF24AA8EFA00541E64 /* SidebarItemView.swift in Sources */,
|
||||||
|
514E6BDA24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */,
|
||||||
51E4990D24A808C500B667CB /* RSHTMLMetadata+Extension.swift in Sources */,
|
51E4990D24A808C500B667CB /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||||
51919FF424AB869C00541E64 /* TimelineItem.swift in Sources */,
|
51919FF424AB869C00541E64 /* TimelineItem.swift in Sources */,
|
||||||
|
514E6C0224AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */,
|
||||||
51E49A0024A91FC100B667CB /* RegularSidebarContainerView.swift in Sources */,
|
51E49A0024A91FC100B667CB /* RegularSidebarContainerView.swift in Sources */,
|
||||||
51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */,
|
51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */,
|
||||||
51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */,
|
51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */,
|
||||||
@ -4737,6 +4776,7 @@
|
|||||||
172199F124AB716900A31D04 /* SidebarToolbar.swift in Sources */,
|
172199F124AB716900A31D04 /* SidebarToolbar.swift in Sources */,
|
||||||
51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */,
|
51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */,
|
||||||
51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */,
|
51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */,
|
||||||
|
514E6BFF24AD255D00AC6F6E /* PreviewArticles.swift in Sources */,
|
||||||
51E4993024A8676400B667CB /* ArticleSorter.swift in Sources */,
|
51E4993024A8676400B667CB /* ArticleSorter.swift in Sources */,
|
||||||
51408B7E24A9EC6F0073CF4E /* SidebarItem.swift in Sources */,
|
51408B7E24A9EC6F0073CF4E /* SidebarItem.swift in Sources */,
|
||||||
51E4990A24A808C500B667CB /* FeaturedImageDownloader.swift in Sources */,
|
51E4990A24A808C500B667CB /* FeaturedImageDownloader.swift in Sources */,
|
||||||
@ -4763,6 +4803,7 @@
|
|||||||
51E4990F24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */,
|
51E4990F24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */,
|
||||||
51E4993124A8676400B667CB /* FetchRequestOperation.swift in Sources */,
|
51E4993124A8676400B667CB /* FetchRequestOperation.swift in Sources */,
|
||||||
51E4992624A80AAB00B667CB /* AppAssets.swift in Sources */,
|
51E4992624A80AAB00B667CB /* AppAssets.swift in Sources */,
|
||||||
|
514E6C0624AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */,
|
||||||
51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */,
|
51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */,
|
||||||
51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */,
|
51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */,
|
||||||
51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */,
|
51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */,
|
||||||
@ -4770,7 +4811,7 @@
|
|||||||
51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */,
|
51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */,
|
||||||
51E499D824A912C200B667CB /* SceneModel.swift in Sources */,
|
51E499D824A912C200B667CB /* SceneModel.swift in Sources */,
|
||||||
51919FB324AAB97900541E64 /* FeedImageLoader.swift in Sources */,
|
51919FB324AAB97900541E64 /* FeedImageLoader.swift in Sources */,
|
||||||
17B223DC24AC24D2001E4592 /* TimelineLayoutView.swift in Sources */,
|
51919FB324AAB97900541E64 /* FeedIconImageLoader.swift in Sources */,
|
||||||
51E4991324A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */,
|
51E4991324A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */,
|
||||||
51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */,
|
51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */,
|
||||||
51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */,
|
51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */,
|
||||||
@ -4817,6 +4858,7 @@
|
|||||||
51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */,
|
51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||||
51919FF824AB8B7700541E64 /* TimelineView.swift in Sources */,
|
51919FF824AB8B7700541E64 /* TimelineView.swift in Sources */,
|
||||||
51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */,
|
51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */,
|
||||||
|
514E6C0724AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */,
|
||||||
51E4994D24A8734C00B667CB /* ExtensionPointIdentifer.swift in Sources */,
|
51E4994D24A8734C00B667CB /* ExtensionPointIdentifer.swift in Sources */,
|
||||||
51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */,
|
51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */,
|
||||||
51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */,
|
51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */,
|
||||||
@ -4842,8 +4884,9 @@
|
|||||||
51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */,
|
51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */,
|
||||||
51E4991C24A8092000B667CB /* NSAttributedString+NetNewsWire.swift in Sources */,
|
51E4991C24A8092000B667CB /* NSAttributedString+NetNewsWire.swift in Sources */,
|
||||||
51E499D924A912C200B667CB /* SceneModel.swift in Sources */,
|
51E499D924A912C200B667CB /* SceneModel.swift in Sources */,
|
||||||
51919FB424AAB97900541E64 /* FeedImageLoader.swift in Sources */,
|
51919FB424AAB97900541E64 /* FeedIconImageLoader.swift in Sources */,
|
||||||
51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */,
|
51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */,
|
||||||
|
514E6C0324AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */,
|
||||||
51E4996D24A8762D00B667CB /* ArticleExtractor.swift in Sources */,
|
51E4996D24A8762D00B667CB /* ArticleExtractor.swift in Sources */,
|
||||||
51E4994024A8713B00B667CB /* AccountRefreshTimer.swift in Sources */,
|
51E4994024A8713B00B667CB /* AccountRefreshTimer.swift in Sources */,
|
||||||
51E49A0424A91FF600B667CB /* SceneNavigationView.swift in Sources */,
|
51E49A0424A91FF600B667CB /* SceneNavigationView.swift in Sources */,
|
||||||
@ -4851,12 +4894,14 @@
|
|||||||
51E498C824A8085D00B667CB /* SmartFeedsController.swift in Sources */,
|
51E498C824A8085D00B667CB /* SmartFeedsController.swift in Sources */,
|
||||||
175942AB24AD533200585066 /* RefreshInterval.swift in Sources */,
|
175942AB24AD533200585066 /* RefreshInterval.swift in Sources */,
|
||||||
51E4992C24A8676300B667CB /* ArticleSorter.swift in Sources */,
|
51E4992C24A8676300B667CB /* ArticleSorter.swift in Sources */,
|
||||||
|
514E6C0A24AD39AD00AC6F6E /* ArticleIconImageLoader.swift in Sources */,
|
||||||
51E4995024A8734C00B667CB /* ExtensionPoint.swift in Sources */,
|
51E4995024A8734C00B667CB /* ExtensionPoint.swift in Sources */,
|
||||||
51E4990E24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */,
|
51E4990E24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */,
|
||||||
51E498FB24A808BA00B667CB /* FaviconGenerator.swift in Sources */,
|
51E498FB24A808BA00B667CB /* FaviconGenerator.swift in Sources */,
|
||||||
51E4996724A8760B00B667CB /* ArticleStylesManager.swift in Sources */,
|
51E4996724A8760B00B667CB /* ArticleStylesManager.swift in Sources */,
|
||||||
1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */,
|
1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */,
|
||||||
51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */,
|
51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */,
|
||||||
|
514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */,
|
||||||
51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */,
|
51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */,
|
||||||
51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */,
|
51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */,
|
||||||
51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */,
|
51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */,
|
||||||
@ -4877,6 +4922,7 @@
|
|||||||
51E4996124A875F400B667CB /* ArticleRenderer.swift in Sources */,
|
51E4996124A875F400B667CB /* ArticleRenderer.swift in Sources */,
|
||||||
51392D1C24AC19A000BE0D35 /* SidebarExpandedContainers.swift in Sources */,
|
51392D1C24AC19A000BE0D35 /* SidebarExpandedContainers.swift in Sources */,
|
||||||
51C0515F24A77DF800194D5E /* MainApp.swift in Sources */,
|
51C0515F24A77DF800194D5E /* MainApp.swift in Sources */,
|
||||||
|
514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */,
|
||||||
1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */,
|
1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */,
|
||||||
1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */,
|
1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */,
|
||||||
51E4992D24A8676300B667CB /* FetchRequestOperation.swift in Sources */,
|
51E4992D24A8676300B667CB /* FetchRequestOperation.swift in Sources */,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user