Update iOS with latest TimelineModel refactoring
This commit is contained in:
parent
7d7a018fe1
commit
882ebbea3e
Multiplatform
Shared
CombineExt
Sidebar
Timeline
iOS/Article
NetNewsWire.xcodeproj
121
Multiplatform/Shared/CombineExt/ReplaySubject.swift
Normal file
121
Multiplatform/Shared/CombineExt/ReplaySubject.swift
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
//
|
||||||
|
// ReplaySubject.swift
|
||||||
|
// CombineExt
|
||||||
|
//
|
||||||
|
// Created by Jasdev Singh on 13/04/2020.
|
||||||
|
// Copyright © 2020 Combine Community. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if canImport(Combine)
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
/// A `ReplaySubject` is a subject that can buffer one or more values. It stores value events, up to its `bufferSize` in a
|
||||||
|
/// first-in-first-out manner and then replays it to
|
||||||
|
/// future subscribers and also forwards completion events.
|
||||||
|
///
|
||||||
|
/// The implementation borrows heavily from [Entwine’s](https://github.com/tcldr/Entwine/blob/b839c9fcc7466878d6a823677ce608da998b95b9/Sources/Entwine/Operators/ReplaySubject.swift).
|
||||||
|
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
|
public final class ReplaySubject<Output, Failure: Error>: Subject {
|
||||||
|
public typealias Output = Output
|
||||||
|
public typealias Failure = Failure
|
||||||
|
|
||||||
|
private let bufferSize: Int
|
||||||
|
private var buffer = [Output]()
|
||||||
|
|
||||||
|
// Keeping track of all live subscriptions, so `send` events can be forwarded to them.
|
||||||
|
private var subscriptions = [Subscription<AnySubscriber<Output, Failure>>]()
|
||||||
|
|
||||||
|
private var completion: Subscribers.Completion<Failure>?
|
||||||
|
private var isActive: Bool { completion == nil }
|
||||||
|
|
||||||
|
/// Create a `ReplaySubject`, buffering up to `bufferSize` values and replaying them to new subscribers
|
||||||
|
/// - Parameter bufferSize: The maximum number of value events to buffer and replay to all future subscribers.
|
||||||
|
public init(bufferSize: Int) {
|
||||||
|
self.bufferSize = bufferSize
|
||||||
|
}
|
||||||
|
|
||||||
|
public func send(_ value: Output) {
|
||||||
|
guard isActive else { return }
|
||||||
|
|
||||||
|
buffer.append(value)
|
||||||
|
|
||||||
|
if buffer.count > bufferSize {
|
||||||
|
buffer.removeFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptions.forEach { $0.forwardValueToBuffer(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func send(completion: Subscribers.Completion<Failure>) {
|
||||||
|
guard isActive else { return }
|
||||||
|
|
||||||
|
self.completion = completion
|
||||||
|
|
||||||
|
subscriptions.forEach { $0.forwardCompletionToBuffer(completion) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func send(subscription: Combine.Subscription) {
|
||||||
|
subscription.request(.unlimited)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func receive<Subscriber: Combine.Subscriber>(subscriber: Subscriber) where Failure == Subscriber.Failure, Output == Subscriber.Input {
|
||||||
|
let subscriberIdentifier = subscriber.combineIdentifier
|
||||||
|
|
||||||
|
let subscription = Subscription(downstream: AnySubscriber(subscriber)) { [weak self] in
|
||||||
|
guard let self = self,
|
||||||
|
let subscriptionIndex = self.subscriptions
|
||||||
|
.firstIndex(where: { $0.innerSubscriberIdentifier == subscriberIdentifier }) else { return }
|
||||||
|
|
||||||
|
self.subscriptions.remove(at: subscriptionIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptions.append(subscription)
|
||||||
|
|
||||||
|
subscriber.receive(subscription: subscription)
|
||||||
|
subscription.replay(buffer, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
|
extension ReplaySubject {
|
||||||
|
final class Subscription<Downstream: Subscriber>: Combine.Subscription where Output == Downstream.Input, Failure == Downstream.Failure {
|
||||||
|
private var demandBuffer: DemandBuffer<Downstream>?
|
||||||
|
private var cancellationHandler: (() -> Void)?
|
||||||
|
|
||||||
|
fileprivate let innerSubscriberIdentifier: CombineIdentifier
|
||||||
|
|
||||||
|
init(downstream: Downstream, cancellationHandler: (() -> Void)?) {
|
||||||
|
self.demandBuffer = DemandBuffer(subscriber: downstream)
|
||||||
|
self.innerSubscriberIdentifier = downstream.combineIdentifier
|
||||||
|
self.cancellationHandler = cancellationHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func replay(_ buffer: [Output], completion: Subscribers.Completion<Failure>?) {
|
||||||
|
buffer.forEach(forwardValueToBuffer)
|
||||||
|
|
||||||
|
if let completion = completion {
|
||||||
|
forwardCompletionToBuffer(completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func forwardValueToBuffer(_ value: Output) {
|
||||||
|
_ = demandBuffer?.buffer(value: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func forwardCompletionToBuffer(_ completion: Subscribers.Completion<Failure>) {
|
||||||
|
demandBuffer?.complete(completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func request(_ demand: Subscribers.Demand) {
|
||||||
|
_ = demandBuffer?.demand(demand)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancel() {
|
||||||
|
cancellationHandler?()
|
||||||
|
cancellationHandler = nil
|
||||||
|
|
||||||
|
demandBuffer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
24
Multiplatform/Shared/CombineExt/ShareReplay.swift
Normal file
24
Multiplatform/Shared/CombineExt/ShareReplay.swift
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// ShareReplay.swift
|
||||||
|
// CombineExt
|
||||||
|
//
|
||||||
|
// Created by Jasdev Singh on 13/04/2020.
|
||||||
|
// Copyright © 2020 Combine Community. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if canImport(Combine)
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
|
public extension Publisher {
|
||||||
|
/// A variation on [share()](https://developer.apple.com/documentation/combine/publisher/3204754-share)
|
||||||
|
/// that allows for buffering and replaying a `replay` amount of value events to future subscribers.
|
||||||
|
///
|
||||||
|
/// - Parameter count: The number of value events to buffer in a first-in-first-out manner.
|
||||||
|
/// - Returns: A publisher that replays the specified number of value events to future subscribers.
|
||||||
|
func share(replay count: Int) -> Publishers.Autoconnect<Publishers.Multicast<Self, ReplaySubject<Output, Failure>>> {
|
||||||
|
multicast { ReplaySubject(bufferSize: count) }
|
||||||
|
.autoconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -75,6 +75,7 @@ private extension SidebarModel {
|
|||||||
.removeDuplicates(by: { previousFeeds, currentFeeds in
|
.removeDuplicates(by: { previousFeeds, currentFeeds in
|
||||||
return previousFeeds.elementsEqual(currentFeeds, by: { $0.feedID == $1.feedID })
|
return previousFeeds.elementsEqual(currentFeeds, by: { $0.feedID == $1.feedID })
|
||||||
})
|
})
|
||||||
|
.share(replay: 1)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ private extension SidebarModel {
|
|||||||
.compactMap { [weak self] _, readFilter, selectedFeeds in
|
.compactMap { [weak self] _, readFilter, selectedFeeds in
|
||||||
self?.rebuildSidebarItems(isReadFiltered: readFilter, selectedFeeds: selectedFeeds)
|
self?.rebuildSidebarItems(isReadFiltered: readFilter, selectedFeeds: selectedFeeds)
|
||||||
}
|
}
|
||||||
.share()
|
.share(replay: 1)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
|||||||
weak var delegate: TimelineModelDelegate?
|
weak var delegate: TimelineModelDelegate?
|
||||||
|
|
||||||
@Published var nameForDisplay = ""
|
@Published var nameForDisplay = ""
|
||||||
@Published var selectedArticleIDs = Set<String>() // Don't use directly. Use selectedArticles
|
@Published var selectedTimelineItemIDs = Set<String>() // Don't use directly. Use selectedTimelineItemsPublisher
|
||||||
@Published var selectedArticleID: String? = nil // Don't use directly. Use selectedArticles
|
@Published var selectedTimelineItemID: String? = nil // Don't use directly. Use selectedTimelineItemsPublisher
|
||||||
@Published var isReadFiltered: Bool? = nil
|
@Published var isReadFiltered: Bool? = nil
|
||||||
|
|
||||||
var timelineItemsPublisher: AnyPublisher<[TimelineItem], Never>?
|
var timelineItemsPublisher: AnyPublisher<[TimelineItem], Never>?
|
||||||
@ -40,15 +40,16 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
|||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
private var sortDirectionSubject = PassthroughSubject<Bool, Never>()
|
private var sortDirectionSubject = ReplaySubject<Bool, Never>(bufferSize: 1)
|
||||||
private var groupByFeedSubject = PassthroughSubject<Bool, Never>()
|
private var groupByFeedSubject = ReplaySubject<Bool, Never>(bufferSize: 1)
|
||||||
|
|
||||||
init(delegate: TimelineModelDelegate) {
|
init(delegate: TimelineModelDelegate) {
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
// subscribeToArticleStatusChanges()
|
|
||||||
subscribeToUserDefaultsChanges()
|
subscribeToUserDefaultsChanges()
|
||||||
|
subscribeToReadFilterChanges()
|
||||||
subscribeToArticleFetchChanges()
|
subscribeToArticleFetchChanges()
|
||||||
subscribeToSelectedArticleSelectionChanges()
|
subscribeToSelectedArticleSelectionChanges()
|
||||||
|
// subscribeToArticleStatusChanges()
|
||||||
// subscribeToAccountDidDownloadArticles()
|
// subscribeToAccountDidDownloadArticles()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +79,31 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
|||||||
// }.store(in: &cancellables)
|
// }.store(in: &cancellables)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
func subscribeToReadFilterChanges() {
|
||||||
|
guard let selectedFeedsPublisher = delegate?.selectedFeedsPublisher else { return }
|
||||||
|
|
||||||
|
selectedFeedsPublisher.sink { [weak self] feeds in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
guard feeds.count == 1, let timelineFeed = feeds.first else {
|
||||||
|
self.isReadFiltered = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard timelineFeed.defaultReadFilterType != .alwaysRead else {
|
||||||
|
self.isReadFiltered = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let feedID = timelineFeed.feedID, let readFilterEnabled = self.readFilterEnabledTable[feedID] {
|
||||||
|
self.isReadFiltered = readFilterEnabled
|
||||||
|
} else {
|
||||||
|
self.isReadFiltered = timelineFeed.defaultReadFilterType == .read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
func subscribeToUserDefaultsChanges() {
|
func subscribeToUserDefaultsChanges() {
|
||||||
let kickStartNote = Notification(name: Notification.Name("Kick Start"))
|
let kickStartNote = Notification(name: Notification.Name("Kick Start"))
|
||||||
NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)
|
NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)
|
||||||
@ -97,11 +123,12 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
|
|||||||
.map { [weak self] feeds -> Set<Article> in
|
.map { [weak self] feeds -> Set<Article> in
|
||||||
return self?.fetchArticles(feeds: feeds) ?? Set<Article>()
|
return self?.fetchArticles(feeds: feeds) ?? Set<Article>()
|
||||||
}
|
}
|
||||||
.combineLatest($isReadFiltered, sortDirectionPublisher, groupByPublisher)
|
.combineLatest(sortDirectionPublisher, groupByPublisher)
|
||||||
.compactMap { [weak self] articles, filtered, sortDirection, groupBy -> [TimelineItem] in
|
.compactMap { [weak self] articles, sortDirection, groupBy -> [TimelineItem] in
|
||||||
let sortedArticles = Array(articles).sortedByDate(sortDirection ? .orderedDescending : .orderedAscending, groupByFeed: groupBy)
|
let sortedArticles = Array(articles).sortedByDate(sortDirection ? .orderedDescending : .orderedAscending, groupByFeed: groupBy)
|
||||||
return self?.buildTimelineItems(articles: sortedArticles) ?? [TimelineItem]()
|
return self?.buildTimelineItems(articles: sortedArticles) ?? [TimelineItem]()
|
||||||
}
|
}
|
||||||
|
.share(replay: 1)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,24 +245,6 @@ private extension TimelineModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Timeline Management
|
// MARK: Timeline Management
|
||||||
|
|
||||||
// func resetReadFilter() {
|
|
||||||
// guard feeds.count == 1, let timelineFeed = feeds.first else {
|
|
||||||
// isReadFiltered = nil
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// guard timelineFeed.defaultReadFilterType != .alwaysRead else {
|
|
||||||
// isReadFiltered = nil
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if let feedID = timelineFeed.feedID, let readFilterEnabled = readFilterEnabledTable[feedID] {
|
|
||||||
// isReadFiltered = readFilterEnabled
|
|
||||||
// } else {
|
|
||||||
// isReadFiltered = timelineFeed.defaultReadFilterType == .read
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
func sortParametersDidChange() {
|
func sortParametersDidChange() {
|
||||||
// performBlockAndRestoreSelection {
|
// performBlockAndRestoreSelection {
|
||||||
|
@ -38,8 +38,8 @@ struct TimelineView: View {
|
|||||||
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
|
||||||
}
|
}
|
||||||
ScrollViewReader { scrollViewProxy in
|
ScrollViewReader { scrollViewProxy in
|
||||||
List(timelineItems, selection: $timelineModel.selectedArticleIDs) { timelineItem in
|
List(timelineItems, selection: $timelineModel.selectedTimelineItemIDs) { timelineItem in
|
||||||
let selected = timelineModel.selectedArticleIDs.contains(timelineItem.article.articleID)
|
let selected = timelineModel.selectedTimelineItemIDs.contains(timelineItem.article.articleID)
|
||||||
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
|
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
|
||||||
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
|
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ struct TimelineView: View {
|
|||||||
timelineItemFrames[pref.articleID] = pref.frame
|
timelineItemFrames[pref.articleID] = pref.frame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: timelineModel.selectedArticleIDs) { selectedArticleIDs in
|
.onChange(of: timelineModel.selectedTimelineItemIDs) { selectedArticleIDs in
|
||||||
let proxyFrame = geometryReaderProxy.frame(in: .global)
|
let proxyFrame = geometryReaderProxy.frame(in: .global)
|
||||||
for articleID in selectedArticleIDs {
|
for articleID in selectedArticleIDs {
|
||||||
if let itemFrame = timelineItemFrames[articleID] {
|
if let itemFrame = timelineItemFrames[articleID] {
|
||||||
@ -70,14 +70,14 @@ struct TimelineView: View {
|
|||||||
.navigationTitle(Text(verbatim: timelineModel.nameForDisplay))
|
.navigationTitle(Text(verbatim: timelineModel.nameForDisplay))
|
||||||
#else
|
#else
|
||||||
ScrollViewReader { scrollViewProxy in
|
ScrollViewReader { scrollViewProxy in
|
||||||
List(timelineModel.timelineItems) { timelineItem in
|
List(timelineItems) { timelineItem in
|
||||||
ZStack {
|
ZStack {
|
||||||
let selected = timelineModel.selectedArticleID == timelineItem.article.articleID
|
let selected = timelineModel.selectedTimelineItemID == timelineItem.article.articleID
|
||||||
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
|
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
|
||||||
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
|
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
|
||||||
NavigationLink(destination: ArticleContainerView(),
|
NavigationLink(destination: ArticleContainerView(),
|
||||||
tag: timelineItem.article.articleID,
|
tag: timelineItem.article.articleID,
|
||||||
selection: $timelineModel.selectedArticleID) {
|
selection: $timelineModel.selectedTimelineItemID) {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}.buttonStyle(PlainButtonStyle())
|
}.buttonStyle(PlainButtonStyle())
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ struct TimelineView: View {
|
|||||||
timelineItemFrames[pref.articleID] = pref.frame
|
timelineItemFrames[pref.articleID] = pref.frame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: timelineModel.selectedArticleID) { selectedArticleID in
|
.onChange(of: timelineModel.selectedTimelineItemID) { selectedArticleID in
|
||||||
let proxyFrame = geometryReaderProxy.frame(in: .global)
|
let proxyFrame = geometryReaderProxy.frame(in: .global)
|
||||||
if let articleID = selectedArticleID, let itemFrame = timelineItemFrames[articleID] {
|
if let articleID = selectedArticleID, let itemFrame = timelineItemFrames[articleID] {
|
||||||
if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 3 {
|
if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 3 {
|
||||||
@ -98,6 +98,12 @@ struct TimelineView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onReceive(timelineModel.timelineItemsPublisher!) { items in
|
||||||
|
// Animations crash on iPadOS right now
|
||||||
|
// withAnimation {
|
||||||
|
timelineItems = items
|
||||||
|
// }
|
||||||
|
}
|
||||||
.navigationBarTitle(Text(verbatim: timelineModel.nameForDisplay), displayMode: .inline)
|
.navigationBarTitle(Text(verbatim: timelineModel.nameForDisplay), displayMode: .inline)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -61,9 +61,9 @@ class ArticleViewController: UIViewController {
|
|||||||
view.bottomAnchor.constraint(equalTo: pageViewController.view.bottomAnchor)
|
view.bottomAnchor.constraint(equalTo: pageViewController.view.bottomAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
selectedArticlesCancellable = sceneModel?.timelineModel.$selectedArticles.sink { [weak self] articles in
|
// selectedArticlesCancellable = sceneModel?.timelineModel.$selectedArticles.sink { [weak self] articles in
|
||||||
self?.articles = articles
|
// self?.articles = articles
|
||||||
}
|
// }
|
||||||
|
|
||||||
let controller = createWebViewController(currentArticle, updateView: true)
|
let controller = createWebViewController(currentArticle, updateView: true)
|
||||||
self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil)
|
self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil)
|
||||||
|
@ -340,6 +340,10 @@
|
|||||||
51A8001324CA0FC700F41F1D /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001124CA0FC700F41F1D /* Sink.swift */; };
|
51A8001324CA0FC700F41F1D /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001124CA0FC700F41F1D /* Sink.swift */; };
|
||||||
51A8001524CA0FEC00F41F1D /* DemandBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */; };
|
51A8001524CA0FEC00F41F1D /* DemandBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */; };
|
||||||
51A8001624CA0FEC00F41F1D /* DemandBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */; };
|
51A8001624CA0FEC00F41F1D /* DemandBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */; };
|
||||||
|
51A8002D24CC451500F41F1D /* ShareReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8002C24CC451500F41F1D /* ShareReplay.swift */; };
|
||||||
|
51A8002E24CC451600F41F1D /* ShareReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8002C24CC451500F41F1D /* ShareReplay.swift */; };
|
||||||
|
51A8005124CC453C00F41F1D /* ReplaySubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8005024CC453C00F41F1D /* ReplaySubject.swift */; };
|
||||||
|
51A8005224CC453C00F41F1D /* ReplaySubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8005024CC453C00F41F1D /* ReplaySubject.swift */; };
|
||||||
51A8FFED24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */; };
|
51A8FFED24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */; };
|
||||||
51A8FFEE24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */; };
|
51A8FFEE24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */; };
|
||||||
51A9A5E12380C4FE0033AADF /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45255226507D200C03939 /* AppDefaults.swift */; };
|
51A9A5E12380C4FE0033AADF /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45255226507D200C03939 /* AppDefaults.swift */; };
|
||||||
@ -2036,6 +2040,8 @@
|
|||||||
51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedDefaultContainer.swift; sourceTree = "<group>"; };
|
51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedDefaultContainer.swift; sourceTree = "<group>"; };
|
||||||
51A8001124CA0FC700F41F1D /* Sink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sink.swift; sourceTree = "<group>"; };
|
51A8001124CA0FC700F41F1D /* Sink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sink.swift; sourceTree = "<group>"; };
|
||||||
51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemandBuffer.swift; sourceTree = "<group>"; };
|
51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemandBuffer.swift; sourceTree = "<group>"; };
|
||||||
|
51A8002C24CC451500F41F1D /* ShareReplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareReplay.swift; sourceTree = "<group>"; };
|
||||||
|
51A8005024CC453C00F41F1D /* ReplaySubject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplaySubject.swift; sourceTree = "<group>"; };
|
||||||
51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WIthLatestFrom.swift; sourceTree = "<group>"; };
|
51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WIthLatestFrom.swift; sourceTree = "<group>"; };
|
||||||
51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerAccountCell.xib; sourceTree = "<group>"; };
|
51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerAccountCell.xib; sourceTree = "<group>"; };
|
||||||
51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerFolderCell.xib; sourceTree = "<group>"; };
|
51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerFolderCell.xib; sourceTree = "<group>"; };
|
||||||
@ -3044,6 +3050,8 @@
|
|||||||
51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */,
|
51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */,
|
||||||
51A8001124CA0FC700F41F1D /* Sink.swift */,
|
51A8001124CA0FC700F41F1D /* Sink.swift */,
|
||||||
51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */,
|
51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */,
|
||||||
|
51A8002C24CC451500F41F1D /* ShareReplay.swift */,
|
||||||
|
51A8005024CC453C00F41F1D /* ReplaySubject.swift */,
|
||||||
);
|
);
|
||||||
path = CombineExt;
|
path = CombineExt;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -4331,46 +4339,46 @@
|
|||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
51314636235A7BBE00387FDC = {
|
51314636235A7BBE00387FDC = {
|
||||||
CreatedOnToolsVersion = 11.2;
|
CreatedOnToolsVersion = 11.2;
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
LastSwiftMigration = 1120;
|
LastSwiftMigration = 1120;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
513C5CE5232571C2003D4054 = {
|
513C5CE5232571C2003D4054 = {
|
||||||
CreatedOnToolsVersion = 11.0;
|
CreatedOnToolsVersion = 11.0;
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
518B2ED12351B3DD00400001 = {
|
518B2ED12351B3DD00400001 = {
|
||||||
CreatedOnToolsVersion = 11.2;
|
CreatedOnToolsVersion = 11.2;
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
TestTargetID = 840D617B2029031C009BC708;
|
TestTargetID = 840D617B2029031C009BC708;
|
||||||
};
|
};
|
||||||
51C0513C24A77DF800194D5E = {
|
51C0513C24A77DF800194D5E = {
|
||||||
CreatedOnToolsVersion = 12.0;
|
CreatedOnToolsVersion = 12.0;
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
51C0514324A77DF800194D5E = {
|
51C0514324A77DF800194D5E = {
|
||||||
CreatedOnToolsVersion = 12.0;
|
CreatedOnToolsVersion = 12.0;
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
6581C73220CED60000F4AD34 = {
|
6581C73220CED60000F4AD34 = {
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
65ED3FA2235DEF6C0081F399 = {
|
65ED3FA2235DEF6C0081F399 = {
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
65ED4090235DEF770081F399 = {
|
65ED4090235DEF770081F399 = {
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
840D617B2029031C009BC708 = {
|
840D617B2029031C009BC708 = {
|
||||||
CreatedOnToolsVersion = 9.3;
|
CreatedOnToolsVersion = 9.3;
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
com.apple.BackgroundModes = {
|
com.apple.BackgroundModes = {
|
||||||
@ -4380,7 +4388,7 @@
|
|||||||
};
|
};
|
||||||
849C645F1ED37A5D003D8FC0 = {
|
849C645F1ED37A5D003D8FC0 = {
|
||||||
CreatedOnToolsVersion = 8.2.1;
|
CreatedOnToolsVersion = 8.2.1;
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
com.apple.HardenedRuntime = {
|
com.apple.HardenedRuntime = {
|
||||||
@ -4390,7 +4398,7 @@
|
|||||||
};
|
};
|
||||||
849C64701ED37A5D003D8FC0 = {
|
849C64701ED37A5D003D8FC0 = {
|
||||||
CreatedOnToolsVersion = 8.2.1;
|
CreatedOnToolsVersion = 8.2.1;
|
||||||
DevelopmentTeam = FQLBNX3GP7;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
TestTargetID = 849C645F1ED37A5D003D8FC0;
|
TestTargetID = 849C645F1ED37A5D003D8FC0;
|
||||||
};
|
};
|
||||||
@ -5241,6 +5249,7 @@
|
|||||||
65082A5224C72B88009FA994 /* SettingsCredentialsAccountModel.swift in Sources */,
|
65082A5224C72B88009FA994 /* SettingsCredentialsAccountModel.swift in Sources */,
|
||||||
172199C924AB228900A31D04 /* SettingsView.swift in Sources */,
|
172199C924AB228900A31D04 /* SettingsView.swift in Sources */,
|
||||||
51B8BCC224C25C3E00360B00 /* SidebarContextMenu.swift in Sources */,
|
51B8BCC224C25C3E00360B00 /* SidebarContextMenu.swift in Sources */,
|
||||||
|
51A8005124CC453C00F41F1D /* ReplaySubject.swift in Sources */,
|
||||||
17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */,
|
17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */,
|
||||||
51B80EB824BD1F8B00C6C32D /* ActivityViewController.swift in Sources */,
|
51B80EB824BD1F8B00C6C32D /* ActivityViewController.swift in Sources */,
|
||||||
51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */,
|
51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */,
|
||||||
@ -5308,6 +5317,7 @@
|
|||||||
51919FEE24AB85E400541E64 /* TimelineContainerView.swift in Sources */,
|
51919FEE24AB85E400541E64 /* TimelineContainerView.swift in Sources */,
|
||||||
653A4E7924BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift in Sources */,
|
653A4E7924BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift in Sources */,
|
||||||
51E4995724A8734D00B667CB /* ExtensionPoint.swift in Sources */,
|
51E4995724A8734D00B667CB /* ExtensionPoint.swift in Sources */,
|
||||||
|
51A8002D24CC451500F41F1D /* ShareReplay.swift in Sources */,
|
||||||
51B8BCE624C25F7C00360B00 /* TimelineContextMenu.swift in Sources */,
|
51B8BCE624C25F7C00360B00 /* TimelineContextMenu.swift in Sources */,
|
||||||
1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */,
|
1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */,
|
||||||
51E4991124A808DE00B667CB /* SmallIconProvider.swift in Sources */,
|
51E4991124A808DE00B667CB /* SmallIconProvider.swift in Sources */,
|
||||||
@ -5361,6 +5371,7 @@
|
|||||||
51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */,
|
51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */,
|
||||||
51B80F1F24BE531200C6C32D /* SharingServiceView.swift in Sources */,
|
51B80F1F24BE531200C6C32D /* SharingServiceView.swift in Sources */,
|
||||||
17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */,
|
17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */,
|
||||||
|
51A8005224CC453C00F41F1D /* ReplaySubject.swift in Sources */,
|
||||||
51E4993324A867E700B667CB /* AppNotifications.swift in Sources */,
|
51E4993324A867E700B667CB /* AppNotifications.swift in Sources */,
|
||||||
51B80F4224BE588200C6C32D /* SharingServicePickerDelegate.swift in Sources */,
|
51B80F4224BE588200C6C32D /* SharingServicePickerDelegate.swift in Sources */,
|
||||||
51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */,
|
51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */,
|
||||||
@ -5418,6 +5429,7 @@
|
|||||||
514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */,
|
514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */,
|
||||||
51B8BCE724C25F7C00360B00 /* TimelineContextMenu.swift in Sources */,
|
51B8BCE724C25F7C00360B00 /* TimelineContextMenu.swift in Sources */,
|
||||||
51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */,
|
51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */,
|
||||||
|
51A8002E24CC451600F41F1D /* ShareReplay.swift in Sources */,
|
||||||
1769E33024BD6271000E1E8E /* EditAccountCredentialsView.swift in Sources */,
|
1769E33024BD6271000E1E8E /* EditAccountCredentialsView.swift in Sources */,
|
||||||
51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */,
|
51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */,
|
||||||
5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */,
|
5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user