mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-29 18:29:27 +01:00
673f0ce718
If a single sync failure is encountered a sheet is presented which allows the user to update their credentials. If multiple sync failures are encountered an alert is shown listing the accounts which encountered errors. On iOS, this alert can take the user into Settings, but there is no obvious way to programatically pesent macOS preferences.
220 lines
5.9 KiB
Swift
220 lines
5.9 KiB
Swift
//
|
|
// SidebarView.swift
|
|
// NetNewsWire
|
|
//
|
|
// Created by Maurice Parker on 6/29/20.
|
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Account
|
|
|
|
struct SidebarView: View {
|
|
|
|
// I had to comment out SceneStorage because it blows up if used on macOS
|
|
// @SceneStorage("expandedContainers") private var expandedContainerData = Data()
|
|
@Environment(\.undoManager) var undoManager
|
|
@StateObject private var expandedContainers = SidebarExpandedContainers()
|
|
@EnvironmentObject private var refreshProgress: RefreshProgressModel
|
|
@EnvironmentObject private var sceneModel: SceneModel
|
|
@EnvironmentObject private var sidebarModel: SidebarModel
|
|
|
|
@State var sidebarItems = [SidebarItem]()
|
|
|
|
private let threshold: CGFloat = 80
|
|
@State private var previousScrollOffset: CGFloat = 0
|
|
@State private var scrollOffset: CGFloat = 0
|
|
@State var pulling: Bool = false
|
|
@State var refreshing: Bool = false
|
|
|
|
|
|
@ViewBuilder var body: some View {
|
|
#if os(macOS)
|
|
VStack {
|
|
HStack {
|
|
Spacer()
|
|
Button (action: {
|
|
withAnimation {
|
|
sidebarModel.isReadFiltered.toggle()
|
|
}
|
|
}, label: {
|
|
if sidebarModel.isReadFiltered {
|
|
AppAssets.filterActiveImage
|
|
} else {
|
|
AppAssets.filterInactiveImage
|
|
}
|
|
})
|
|
.padding(.top, 8).padding(.trailing)
|
|
.buttonStyle(PlainButtonStyle())
|
|
.help(sidebarModel.isReadFiltered ? "Show Read Feeds" : "Filter Read Feeds")
|
|
}
|
|
List(selection: $sidebarModel.selectedFeedIdentifiers) {
|
|
rows
|
|
}
|
|
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))
|
|
}
|
|
}
|
|
.onAppear {
|
|
sidebarModel.undoManager = undoManager
|
|
}
|
|
.onReceive(sidebarModel.sidebarItemsPublisher!) { newItems in
|
|
sidebarItems = newItems
|
|
}
|
|
#else
|
|
ZStack(alignment: .top) {
|
|
List {
|
|
rows
|
|
}
|
|
.background(RefreshFixedView())
|
|
.navigationTitle(Text("Feeds"))
|
|
.onPreferenceChange(RefreshKeyTypes.PrefKey.self) { values in
|
|
refreshLogic(values: values)
|
|
}
|
|
if pulling {
|
|
ProgressView().offset(y: -40)
|
|
}
|
|
}
|
|
.onAppear {
|
|
sidebarModel.undoManager = undoManager
|
|
}
|
|
.onReceive(sidebarModel.sidebarItemsPublisher!) { newItems in
|
|
withAnimation {
|
|
sidebarItems = newItems
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// .onAppear {
|
|
// expandedContainers.data = expandedContainerData
|
|
// }
|
|
// .onReceive(expandedContainers.objectDidChange) {
|
|
// expandedContainerData = expandedContainers.data
|
|
// }
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
// Crossing the threshold on the way UP, we end the refresh
|
|
if pulling && previousScrollOffset > threshold && scrollOffset <= threshold {
|
|
pulling = false
|
|
}
|
|
|
|
// 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 {
|
|
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)
|
|
SidebarItemView(sidebarItem: sidebarItem)
|
|
.padding(.leading, 4)
|
|
.environmentObject(sidebarModel)
|
|
#else
|
|
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))])
|
|
.environmentObject(sidebarModel)
|
|
}
|
|
} else {
|
|
SidebarItemView(sidebarItem: sidebarItem)
|
|
.environmentObject(sidebarModel)
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
}
|
|
|
|
}
|