NetNewsWire/Multiplatform/Shared/Sidebar/SidebarView.swift

220 lines
5.9 KiB
Swift
Raw Normal View History

2020-06-28 21:21:43 +02:00
//
// SidebarView.swift
// NetNewsWire
//
// Created by Maurice Parker on 6/29/20.
2020-06-28 21:21:43 +02:00
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
2020-06-28 21:21:43 +02:00
struct SidebarView: View {
// I had to comment out SceneStorage because it blows up if used on macOS
// @SceneStorage("expandedContainers") private var expandedContainerData = Data()
2020-07-21 10:05:31 +02:00
@Environment(\.undoManager) var undoManager
@StateObject private var expandedContainers = SidebarExpandedContainers()
@EnvironmentObject private var refreshProgress: RefreshProgressModel
@EnvironmentObject private var sceneModel: SceneModel
@EnvironmentObject private var sidebarModel: SidebarModel
2020-07-23 18:53:59 +02:00
@State var sidebarItems = [SidebarItem]()
2020-07-21 10:05:31 +02:00
2020-07-15 23:52:57 +02:00
private let threshold: CGFloat = 80
@State private var previousScrollOffset: CGFloat = 0
@State private var scrollOffset: CGFloat = 0
@State var pulling: Bool = false
2020-07-15 23:52:57 +02:00
@State var refreshing: Bool = false
2020-07-12 01:22:47 +02:00
@ViewBuilder var body: some View {
#if os(macOS)
2020-07-12 17:52:42 +02:00
VStack {
HStack {
Spacer()
Button (action: {
withAnimation {
sidebarModel.isReadFiltered.toggle()
}
}, label: {
if sidebarModel.isReadFiltered {
AppAssets.filterActiveImage
} else {
AppAssets.filterInactiveImage
}
})
2020-07-12 21:43:52 +02:00
.padding(.top, 8).padding(.trailing)
2020-07-12 17:52:42 +02:00
.buttonStyle(PlainButtonStyle())
.help(sidebarModel.isReadFiltered ? "Show Read Feeds" : "Filter Read Feeds")
2020-07-12 17:52:42 +02:00
}
List(selection: $sidebarModel.selectedFeedIdentifiers) {
rows
2020-07-12 17:52:42 +02:00
}
if case .refreshProgress(let percent) = refreshProgress.state {
HStack(alignment: .center) {
Spacer()
ProgressView(value: percent).frame(width: 100)
Spacer()
}
.padding(8)
.background(Color(NSColor.windowBackgroundColor))
.frame(height: 30)
.animation(.easeInOut(duration: 0.5))
.transition(.move(edge: .bottom))
}
}
2020-07-21 10:05:31 +02:00
.onAppear {
sidebarModel.undoManager = undoManager
}
2020-07-23 18:53:59 +02:00
.onReceive(sidebarModel.sidebarItemsPublisher!) { newItems in
sidebarItems = newItems
}
#else
2020-07-15 23:52:57 +02:00
ZStack(alignment: .top) {
List {
rows
}
.background(RefreshFixedView())
.navigationTitle(Text("Feeds"))
.onPreferenceChange(RefreshKeyTypes.PrefKey.self) { values in
refreshLogic(values: values)
}
if pulling {
2020-07-15 23:52:57 +02:00
ProgressView().offset(y: -40)
}
}
2020-07-21 10:05:31 +02:00
.onAppear {
sidebarModel.undoManager = undoManager
}
2020-07-23 18:53:59 +02:00
.onReceive(sidebarModel.sidebarItemsPublisher!) { newItems in
withAnimation {
sidebarItems = newItems
}
}
#endif
2020-07-21 10:05:31 +02:00
// .onAppear {
// expandedContainers.data = expandedContainerData
// }
// .onReceive(expandedContainers.objectDidChange) {
// expandedContainerData = expandedContainers.data
// }
}
2020-07-15 23:52:57 +02:00
func refreshLogic(values: [RefreshKeyTypes.PrefData]) {
DispatchQueue.main.async {
let movingBounds = values.first { $0.vType == .movingView }?.bounds ?? .zero
let fixedBounds = values.first { $0.vType == .fixedView }?.bounds ?? .zero
scrollOffset = movingBounds.minY - fixedBounds.minY
// Crossing the threshold on the way down, we start the refresh process
if !pulling && (scrollOffset > threshold && previousScrollOffset <= threshold) {
pulling = true
AccountManager.shared.refreshAll()
2020-07-15 23:52:57 +02:00
}
// Crossing the threshold on the way UP, we end the refresh
if pulling && previousScrollOffset > threshold && scrollOffset <= threshold {
pulling = false
2020-07-15 23:52:57 +02:00
}
// Update last scroll offset
self.previousScrollOffset = self.scrollOffset
}
}
struct RefreshFixedView: View {
var body: some View {
GeometryReader { proxy in
Color.clear.preference(key: RefreshKeyTypes.PrefKey.self, value: [RefreshKeyTypes.PrefData(vType: .fixedView, bounds: proxy.frame(in: .global))])
}
}
}
struct RefreshKeyTypes {
enum ViewType: Int {
case movingView
case fixedView
}
struct PrefData: Equatable {
let vType: ViewType
let bounds: CGRect
}
struct PrefKey: PreferenceKey {
static var defaultValue: [PrefData] = []
static func reduce(value: inout [PrefData], nextValue: () -> [PrefData]) {
value.append(contentsOf: nextValue())
}
typealias Value = [PrefData]
}
}
var rows: some View {
2020-07-23 18:53:59 +02:00
ForEach(sidebarItems) { sidebarItem in
if let containerID = sidebarItem.containerID {
DisclosureGroup(isExpanded: $expandedContainers[containerID]) {
ForEach(sidebarItem.children) { sidebarItem in
if let containerID = sidebarItem.containerID {
DisclosureGroup(isExpanded: $expandedContainers[containerID]) {
ForEach(sidebarItem.children) { sidebarItem in
SidebarItemNavigation(sidebarItem: sidebarItem)
}
} label: {
SidebarItemNavigation(sidebarItem: sidebarItem)
}
} else {
SidebarItemNavigation(sidebarItem: sidebarItem)
}
}
} label: {
#if os(macOS)
2020-07-21 10:05:31 +02:00
SidebarItemView(sidebarItem: sidebarItem)
.padding(.leading, 4)
.environmentObject(sidebarModel)
#else
2020-07-15 23:52:57 +02:00
if sidebarItem.representedType == .smartFeedController {
GeometryReader { proxy in
SidebarItemView(sidebarItem: sidebarItem)
.preference(key: RefreshKeyTypes.PrefKey.self, value: [RefreshKeyTypes.PrefData(vType: .movingView, bounds: proxy.frame(in: .global))])
2020-07-21 10:05:31 +02:00
.environmentObject(sidebarModel)
2020-07-15 23:52:57 +02:00
}
} else {
SidebarItemView(sidebarItem: sidebarItem)
2020-07-21 10:05:31 +02:00
.environmentObject(sidebarModel)
2020-07-15 23:52:57 +02:00
}
#endif
}
}
2020-06-29 13:16:48 +02:00
}
}
struct SidebarItemNavigation: View {
@EnvironmentObject private var sidebarModel: SidebarModel
var sidebarItem: SidebarItem
@ViewBuilder var body: some View {
#if os(macOS)
SidebarItemView(sidebarItem: sidebarItem)
.tag(sidebarItem.feed!.feedID!)
#else
ZStack {
SidebarItemView(sidebarItem: sidebarItem)
NavigationLink(destination: TimelineContainerView(),
tag: sidebarItem.feed!.feedID!,
selection: $sidebarModel.selectedFeedIdentifier) {
EmptyView()
}.buttonStyle(PlainButtonStyle())
}
#endif
}
}
2020-06-28 21:21:43 +02:00
}