NetNewsWire/Multiplatform/Shared/Timeline/TimelineView.swift

143 lines
4.6 KiB
Swift
Raw Normal View History

2020-06-30 18:03:33 +02:00
//
// TimelineView.swift
// NetNewsWire
//
// Created by Maurice Parker on 6/30/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
struct TimelineView: View {
2020-07-01 18:13:11 +02:00
@EnvironmentObject private var timelineModel: TimelineModel
@State private var timelineItems = OrderedDictionary<String, TimelineItem>()
@State private var timelineItemFrames = [String: CGRect]()
2020-07-12 01:22:47 +02:00
@ViewBuilder var body: some View {
GeometryReader { geometryReaderProxy in
#if os(macOS)
VStack {
HStack {
TimelineSortOrderView()
Spacer()
Button (action: {
withAnimation {
timelineModel.toggleReadFilter()
}
}, label: {
if timelineModel.isReadFiltered ?? false {
AppAssets.filterActiveImage
} else {
AppAssets.filterInactiveImage
}
})
.hidden(timelineModel.isReadFiltered == nil)
.padding(.top, 8).padding(.trailing)
.buttonStyle(PlainButtonStyle())
.help(timelineModel.isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
}
ScrollViewReader { scrollViewProxy in
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 {
timelineItemFrames[pref.articleID] = pref.frame
}
}
.onChange(of: timelineModel.selectedTimelineItemIDs) { selectedArticleIDs in
let proxyFrame = geometryReaderProxy.frame(in: .global)
for articleID in selectedArticleIDs {
if let itemFrame = timelineItemFrames[articleID] {
if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 35 {
withAnimation {
scrollViewProxy.scrollTo(articleID, anchor: .center)
}
}
}
}
}
2020-07-12 21:43:52 +02:00
}
}
.onReceive(timelineModel.timelineItemsPublisher!) { items in
withAnimation {
timelineItems = items
}
}
.navigationTitle(Text(verbatim: timelineModel.nameForDisplay))
#else
ScrollViewReader { scrollViewProxy in
List(timelineItems) { timelineItem in
ZStack {
let selected = timelineModel.selectedTimelineItemID == timelineItem.article.articleID
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem))
NavigationLink(destination: ArticleContainerView(),
tag: timelineItem.article.articleID,
selection: $timelineModel.selectedTimelineItemID) {
EmptyView()
}.buttonStyle(PlainButtonStyle())
}
}
.onPreferenceChange(TimelineItemFramePreferenceKey.self) { preferences in
for pref in preferences {
timelineItemFrames[pref.articleID] = pref.frame
}
}
.onChange(of: timelineModel.selectedTimelineItemID) { selectedArticleID in
let proxyFrame = geometryReaderProxy.frame(in: .global)
if let articleID = selectedArticleID, let itemFrame = timelineItemFrames[articleID] {
if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 3 {
withAnimation {
scrollViewProxy.scrollTo(articleID, anchor: .center)
}
}
}
}
}
.onReceive(timelineModel.timelineItemsPublisher!) { items in
// Animations crash on iPadOS right now
// withAnimation {
timelineItems = items
// }
}
.navigationBarTitle(Text(verbatim: timelineModel.nameForDisplay), displayMode: .inline)
#endif
2020-07-12 01:22:47 +02:00
}
2020-06-30 18:03:33 +02:00
}
2020-07-12 01:22:47 +02:00
2020-06-30 18:03:33 +02:00
}
struct TimelineItemFramePreferenceKey: PreferenceKey {
typealias Value = [TimelineItemFramePreference]
static var defaultValue: [TimelineItemFramePreference] = []
static func reduce(value: inout [TimelineItemFramePreference], nextValue: () -> [TimelineItemFramePreference]) {
value.append(contentsOf: nextValue())
}
}
struct TimelineItemFramePreference: Equatable {
let articleID: String
let frame: CGRect
}
struct TimelineItemFramePreferenceView: View {
let timelineItem: TimelineItem
var body: some View {
GeometryReader { proxy in
Rectangle()
.fill(Color.clear)
.preference(key: TimelineItemFramePreferenceKey.self,
value: [TimelineItemFramePreference(articleID: timelineItem.article.articleID, frame: proxy.frame(in: .global))])
}
}
}