mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-10 17:02:54 +01:00
Switch TimelineItems to use an OrderedDictionary
This commit is contained in:
parent
882ebbea3e
commit
e88e4f65a5
@ -30,7 +30,7 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
||||
@Published var selectedTimelineItemID: String? = nil // Don't use directly. Use selectedTimelineItemsPublisher
|
||||
@Published var isReadFiltered: Bool? = nil
|
||||
|
||||
var timelineItemsPublisher: AnyPublisher<[TimelineItem], Never>?
|
||||
var timelineItemsPublisher: AnyPublisher<OrderedDictionary<String, TimelineItem>, Never>?
|
||||
var selectedTimelineItemsPublisher: AnyPublisher<[TimelineItem], Never>?
|
||||
|
||||
var readFilterEnabledTable = [FeedIdentifier: Bool]()
|
||||
@ -124,9 +124,9 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
||||
return self?.fetchArticles(feeds: feeds) ?? Set<Article>()
|
||||
}
|
||||
.combineLatest(sortDirectionPublisher, groupByPublisher)
|
||||
.compactMap { [weak self] articles, sortDirection, groupBy -> [TimelineItem] in
|
||||
.compactMap { [weak self] articles, sortDirection, groupBy in
|
||||
let sortedArticles = Array(articles).sortedByDate(sortDirection ? .orderedDescending : .orderedAscending, groupByFeed: groupBy)
|
||||
return self?.buildTimelineItems(articles: sortedArticles) ?? [TimelineItem]()
|
||||
return self?.buildTimelineItems(articles: sortedArticles) ?? OrderedDictionary<String, TimelineItem>()
|
||||
}
|
||||
.share(replay: 1)
|
||||
.eraseToAnyPublisher()
|
||||
@ -284,10 +284,11 @@ private extension TimelineModel {
|
||||
return fetchedArticles
|
||||
}
|
||||
|
||||
func buildTimelineItems(articles: [Article]) -> [TimelineItem] {
|
||||
var items = [TimelineItem]()
|
||||
func buildTimelineItems(articles: [Article]) -> OrderedDictionary<String, TimelineItem> {
|
||||
var items = OrderedDictionary<String, TimelineItem>()
|
||||
for (index, article) in articles.enumerated() {
|
||||
items.append(TimelineItem(index: index, article: article))
|
||||
let item = TimelineItem(index: index, article: article)
|
||||
items[item.id] = item
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import SwiftUI
|
||||
struct TimelineView: View {
|
||||
|
||||
@EnvironmentObject private var timelineModel: TimelineModel
|
||||
@State private var timelineItems = [TimelineItem]()
|
||||
@State private var timelineItems = OrderedDictionary<String, TimelineItem>()
|
||||
@State private var timelineItemFrames = [String: CGRect]()
|
||||
|
||||
@ViewBuilder var body: some View {
|
||||
@ -38,10 +38,12 @@ struct TimelineView: View {
|
||||
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
||||
}
|
||||
ScrollViewReader { scrollViewProxy in
|
||||
List(timelineItems, selection: $timelineModel.selectedTimelineItemIDs) { timelineItem in
|
||||
let selected = timelineModel.selectedTimelineItemIDs.contains(timelineItem.article.articleID)
|
||||
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
|
||||
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
|
||||
List(timelineItems.keys, id: \.self, selection: $timelineModel.selectedTimelineItemIDs) { timelineItemID in
|
||||
if let timelineItem = timelineItems[timelineItemID] {
|
||||
let selected = timelineModel.selectedTimelineItemIDs.contains(timelineItem.article.articleID)
|
||||
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
|
||||
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
|
||||
}
|
||||
}
|
||||
.onPreferenceChange(TimelineItemFramePreferenceKey.self) { preferences in
|
||||
for pref in preferences {
|
||||
|
@ -477,6 +477,11 @@
|
||||
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; };
|
||||
51C452B42265141B00C03939 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51C452B32265141B00C03939 /* WebKit.framework */; };
|
||||
51C452B82265178500C03939 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 51C452B72265178500C03939 /* styleSheet.css */; };
|
||||
51C65AD524CC834F008EB3BD /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C65AD424CC834F008EB3BD /* OrderedDictionary.swift */; };
|
||||
51C65AD624CC834F008EB3BD /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C65AD424CC834F008EB3BD /* OrderedDictionary.swift */; };
|
||||
51C65AD724CC834F008EB3BD /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C65AD424CC834F008EB3BD /* OrderedDictionary.swift */; };
|
||||
51C65AD824CC834F008EB3BD /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C65AD424CC834F008EB3BD /* OrderedDictionary.swift */; };
|
||||
51C65AD924CC834F008EB3BD /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C65AD424CC834F008EB3BD /* OrderedDictionary.swift */; };
|
||||
51C9DE5823EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C9DE5723EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift */; };
|
||||
51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */; };
|
||||
51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CE1C0A23622006005548FC /* RefreshProgressView.swift */; };
|
||||
@ -2108,6 +2113,7 @@
|
||||
51C4528B2265095F00C03939 /* AddFolderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderViewController.swift; sourceTree = "<group>"; };
|
||||
51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = "<group>"; };
|
||||
51C65AD424CC834F008EB3BD /* OrderedDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedDictionary.swift; sourceTree = "<group>"; };
|
||||
51C9DE5723EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrapperScriptMessageHandler.swift; sourceTree = "<group>"; };
|
||||
51CE1C0823621EDA005548FC /* RefreshProgressView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RefreshProgressView.xib; sourceTree = "<group>"; };
|
||||
51CE1C0A23622006005548FC /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = "<group>"; };
|
||||
@ -3582,6 +3588,7 @@
|
||||
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */,
|
||||
84411E701FE5FBFA004B527F /* SmallIconProvider.swift */,
|
||||
51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */,
|
||||
51C65AD424CC834F008EB3BD /* OrderedDictionary.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -5247,6 +5254,7 @@
|
||||
51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */,
|
||||
5177471024B3029400EB0F74 /* ArticleViewController.swift in Sources */,
|
||||
65082A5224C72B88009FA994 /* SettingsCredentialsAccountModel.swift in Sources */,
|
||||
51C65AD824CC834F008EB3BD /* OrderedDictionary.swift in Sources */,
|
||||
172199C924AB228900A31D04 /* SettingsView.swift in Sources */,
|
||||
51B8BCC224C25C3E00360B00 /* SidebarContextMenu.swift in Sources */,
|
||||
51A8005124CC453C00F41F1D /* ReplaySubject.swift in Sources */,
|
||||
@ -5385,6 +5393,7 @@
|
||||
1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */,
|
||||
51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */,
|
||||
51919FF224AB864A00541E64 /* TimelineModel.swift in Sources */,
|
||||
51C65AD924CC834F008EB3BD /* OrderedDictionary.swift in Sources */,
|
||||
51E4991A24A8090F00B667CB /* IconImage.swift in Sources */,
|
||||
1799E6AA24C2F93F00511E91 /* InspectorPlatformModifier.swift in Sources */,
|
||||
51B8104624C0E6D200C6C32D /* TimelineTextSizer.swift in Sources */,
|
||||
@ -5640,6 +5649,7 @@
|
||||
65ED403F235DEF6C0081F399 /* ArticleRenderer.swift in Sources */,
|
||||
65ED4040235DEF6C0081F399 /* GeneralPrefencesViewController.swift in Sources */,
|
||||
179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */,
|
||||
51C65AD624CC834F008EB3BD /* OrderedDictionary.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -5797,6 +5807,7 @@
|
||||
D3555BF524664566005E48C3 /* ArticleSearchBar.swift in Sources */,
|
||||
B24E9ADE245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */,
|
||||
C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */,
|
||||
51C65AD724CC834F008EB3BD /* OrderedDictionary.swift in Sources */,
|
||||
51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */,
|
||||
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */,
|
||||
84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
|
||||
@ -5928,6 +5939,7 @@
|
||||
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */,
|
||||
511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */,
|
||||
84C9FC7722629E1200D921D6 /* AdvancedPreferencesViewController.swift in Sources */,
|
||||
51C65AD524CC834F008EB3BD /* OrderedDictionary.swift in Sources */,
|
||||
849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */,
|
||||
5108F6B62375E612001ABC45 /* CacheCleaner.swift in Sources */,
|
||||
849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */,
|
||||
|
64
Shared/Extensions/OrderedDictionary.swift
Normal file
64
Shared/Extensions/OrderedDictionary.swift
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// OrderedDictionary.swift
|
||||
// SwiftDataStructures
|
||||
//
|
||||
// Created by Tim Ekl on 6/2/14.
|
||||
// Copyright (c) 2014 Tim Ekl. Available under MIT License. See LICENSE.md.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct OrderedDictionary<Tk: Hashable, Tv> {
|
||||
var keys: Array<Tk> = []
|
||||
var values: Dictionary<Tk,Tv> = [:]
|
||||
|
||||
var count: Int {
|
||||
assert(keys.count == values.count, "Keys and values array out of sync")
|
||||
return self.keys.count;
|
||||
}
|
||||
|
||||
// Explicitly define an empty initializer to prevent the default memberwise initializer from being generated
|
||||
init() {}
|
||||
|
||||
subscript(index: Int) -> Tv? {
|
||||
get {
|
||||
let key = self.keys[index]
|
||||
return self.values[key]
|
||||
}
|
||||
set(newValue) {
|
||||
let key = self.keys[index]
|
||||
if (newValue != nil) {
|
||||
self.values[key] = newValue
|
||||
} else {
|
||||
self.values[key] = nil
|
||||
self.keys.remove(at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscript(key: Tk) -> Tv? {
|
||||
get {
|
||||
return self.values[key]
|
||||
}
|
||||
set(newValue) {
|
||||
if newValue == nil {
|
||||
self.values[key] = nil
|
||||
self.keys = self.keys.filter {$0 != key}
|
||||
} else {
|
||||
let oldValue = self.values.updateValue(newValue!, forKey: key)
|
||||
if oldValue == nil {
|
||||
self.keys.append(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
var result = "{\n"
|
||||
for i in 0..<self.count {
|
||||
result += "[\(i)]: \(self.keys[i]) => \(String(describing: self[i]))\n"
|
||||
}
|
||||
result += "}"
|
||||
return result
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user