Switch to using LazyVStack for the Timeline

This commit is contained in:
Maurice Parker 2020-07-27 08:19:34 -05:00
parent dbe662b7db
commit 9878ca4b17
2 changed files with 62 additions and 51 deletions

View File

@ -17,13 +17,6 @@ struct TimelineItemView: View {
var width: CGFloat var width: CGFloat
var timelineItem: TimelineItem var timelineItem: TimelineItem
#if os(macOS)
var verticalPadding: CGFloat = 10
#endif
#if os(iOS)
var verticalPadding: CGFloat = 0
#endif
var body: some View { var body: some View {
HStack(alignment: .top) { HStack(alignment: .top) {
TimelineItemStatusView(selected: selected, status: timelineItem.status) TimelineItemStatusView(selected: selected, status: timelineItem.status)
@ -65,7 +58,8 @@ struct TimelineItemView: View {
} }
} }
} }
.padding(.vertical, verticalPadding) .padding(.vertical, 10)
.padding(.horizontal)
.onAppear { .onAppear {
articleIconImageLoader.loadImage(for: timelineItem.article) articleIconImageLoader.loadImage(for: timelineItem.article)
} }

View File

@ -15,6 +15,7 @@ struct TimelineView: View {
@EnvironmentObject private var timelineModel: TimelineModel @EnvironmentObject private var timelineModel: TimelineModel
@State private var navigate = false
@State private var timelineItemFrames = [String: CGRect]() @State private var timelineItemFrames = [String: CGRect]()
@ViewBuilder var body: some View { @ViewBuilder var body: some View {
@ -40,24 +41,31 @@ struct TimelineView: View {
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
.help(isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles") .help(isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles")
} }
ScrollViewReader { scrollViewProxy in ScrollView {
List(timelineItems.items, selection: $timelineModel.selectedTimelineItemIDs) { timelineItem in ScrollViewReader { scrollViewProxy in
let selected = timelineModel.selectedTimelineItemIDs.contains(timelineItem.article.articleID) LazyVStack {
TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem) ForEach(timelineItems.items) { timelineItem in
.background(TimelineItemFramePreferenceView(timelineItem: timelineItem)) let selected = timelineModel.selectedTimelineItemIDs.contains(timelineItem.article.articleID)
} TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
.onPreferenceChange(TimelineItemFramePreferenceKey.self) { preferences in .background(TimelineItemBackgroundView(selected: selected, timelineItem: timelineItem))
for pref in preferences { .onTapGesture {
timelineItemFrames[pref.articleID] = pref.frame timelineModel.selectedTimelineItemIDs = Set([timelineItem.id])
} }
} }
.onChange(of: timelineModel.selectedTimelineItemIDs) { selectedArticleIDs in .onPreferenceChange(TimelineItemFramePreferenceKey.self) { preferences in
let proxyFrame = geometryReaderProxy.frame(in: .global) for pref in preferences {
for articleID in selectedArticleIDs { timelineItemFrames[pref.articleID] = pref.frame
if let itemFrame = timelineItemFrames[articleID] { }
if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 35 { }
withAnimation { .onChange(of: timelineModel.selectedTimelineItemIDs) { selectedArticleIDs in
scrollViewProxy.scrollTo(articleID, anchor: .center) 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)
}
}
} }
} }
} }
@ -67,30 +75,36 @@ struct TimelineView: View {
} }
.navigationTitle(Text(verbatim: timelineModel.nameForDisplay)) .navigationTitle(Text(verbatim: timelineModel.nameForDisplay))
#else #else
ScrollViewReader { scrollViewProxy in ZStack {
List(timelineItems.items) { timelineItem in NavigationLink(destination: ArticleView(), isActive: $navigate) {
ZStack { EmptyView()
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 ScrollView {
for pref in preferences { ScrollViewReader { scrollViewProxy in
timelineItemFrames[pref.articleID] = pref.frame LazyVStack {
} ForEach(timelineItems.items) { timelineItem in
} let selected = timelineModel.selectedTimelineItemID == timelineItem.article.articleID
.onChange(of: timelineModel.selectedTimelineItemID) { selectedArticleID in TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem)
let proxyFrame = geometryReaderProxy.frame(in: .global) .background(TimelineItemBackgroundView(selected: selected, timelineItem: timelineItem))
if let articleID = selectedArticleID, let itemFrame = timelineItemFrames[articleID] { .onTapGesture {
if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 3 { timelineModel.selectedTimelineItemIDs = Set([timelineItem.id])
withAnimation { navigate = true
scrollViewProxy.scrollTo(articleID, anchor: .center) }
}
}
.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)
}
}
} }
} }
} }
@ -118,13 +132,16 @@ struct TimelineItemFramePreference: Equatable {
let frame: CGRect let frame: CGRect
} }
struct TimelineItemFramePreferenceView: View { struct TimelineItemBackgroundView: View {
let selected: Bool
let timelineItem: TimelineItem let timelineItem: TimelineItem
var body: some View { var body: some View {
GeometryReader { proxy in GeometryReader { proxy in
Rectangle() Rectangle()
.fill(Color.clear) .fill(selected ? Color.accentColor : Color.clear)
.cornerRadius(6)
.preference(key: TimelineItemFramePreferenceKey.self, .preference(key: TimelineItemFramePreferenceKey.self,
value: [TimelineItemFramePreference(articleID: timelineItem.article.articleID, frame: proxy.frame(in: .global))]) value: [TimelineItemFramePreference(articleID: timelineItem.article.articleID, frame: proxy.frame(in: .global))])
} }