2020-06-28 21:21:43 +02:00
|
|
|
//
|
|
|
|
// SidebarView.swift
|
|
|
|
// NetNewsWire
|
|
|
|
//
|
2020-06-29 20:14:03 +02:00
|
|
|
// 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
|
2020-07-01 03:23:22 +02:00
|
|
|
import Account
|
2020-06-28 21:21:43 +02:00
|
|
|
|
|
|
|
struct SidebarView: View {
|
2020-06-29 00:43:20 +02:00
|
|
|
|
2020-07-26 14:18:05 +02:00
|
|
|
@Binding var sidebarItems: [SidebarItem]
|
|
|
|
|
2020-07-16 04:24:22 +02:00
|
|
|
@EnvironmentObject private var refreshProgress: RefreshProgressModel
|
2020-07-16 03:06:29 +02:00
|
|
|
@EnvironmentObject private var sceneModel: SceneModel
|
2020-06-29 20:14:03 +02:00
|
|
|
@EnvironmentObject private var sidebarModel: SidebarModel
|
2020-07-23 18:53:59 +02:00
|
|
|
|
2020-07-26 14:18:05 +02:00
|
|
|
// I had to comment out SceneStorage because it blows up if used on macOS
|
|
|
|
// @SceneStorage("expandedContainers") private var expandedContainerData = Data()
|
|
|
|
|
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
|
2020-07-16 04:24:22 +02:00
|
|
|
@State var pulling: Bool = false
|
2020-07-15 23:52:57 +02:00
|
|
|
@State var refreshing: Bool = false
|
2020-07-16 04:24:22 +02:00
|
|
|
|
2020-08-12 15:04:56 +02:00
|
|
|
var body: some View {
|
2020-07-08 15:39:39 +02:00
|
|
|
#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())
|
2020-07-12 22:03:43 +02:00
|
|
|
.help(sidebarModel.isReadFiltered ? "Show Read Feeds" : "Filter Read Feeds")
|
2020-07-12 17:52:42 +02:00
|
|
|
}
|
2020-07-20 03:34:20 +02:00
|
|
|
List(selection: $sidebarModel.selectedFeedIdentifiers) {
|
|
|
|
rows
|
2020-07-12 17:52:42 +02:00
|
|
|
}
|
2020-07-20 03:34:20 +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-11 19:47:13 +02:00
|
|
|
}
|
2020-07-08 15:39:39 +02:00
|
|
|
}
|
2020-08-15 03:37:18 +02:00
|
|
|
.alert(isPresented: $sidebarModel.showDeleteConfirmation, content: {
|
|
|
|
Alert(title: sidebarModel.countOfFeedsToDelete() > 1 ?
|
|
|
|
(Text("Delete multiple items?")) :
|
|
|
|
(Text("Delete \(sidebarModel.namesOfFeedsToDelete())?")),
|
|
|
|
message: Text("Are you sure you wish to delete \(sidebarModel.namesOfFeedsToDelete())?"),
|
|
|
|
primaryButton: .destructive(Text("Delete"),
|
|
|
|
action: {
|
|
|
|
sidebarModel.deleteFromAccount.send(sidebarModel.sidebarItemToDelete!)
|
|
|
|
sidebarModel.sidebarItemToDelete = nil
|
|
|
|
sidebarModel.selectedFeedIdentifiers.removeAll()
|
|
|
|
sidebarModel.showDeleteConfirmation = false
|
|
|
|
}),
|
|
|
|
secondaryButton: .cancel(Text("Cancel"), action: {
|
|
|
|
sidebarModel.sidebarItemToDelete = nil
|
|
|
|
sidebarModel.showDeleteConfirmation = false
|
|
|
|
}))
|
|
|
|
})
|
2020-07-08 15:39:39 +02:00
|
|
|
#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)
|
|
|
|
}
|
2020-07-16 04:24:22 +02:00
|
|
|
if pulling {
|
2020-07-15 23:52:57 +02:00
|
|
|
ProgressView().offset(y: -40)
|
|
|
|
}
|
|
|
|
}
|
2020-08-15 03:37:18 +02:00
|
|
|
.alert(isPresented: $sidebarModel.showDeleteConfirmation, content: {
|
|
|
|
Alert(title: sidebarModel.countOfFeedsToDelete() > 1 ?
|
|
|
|
(Text("Delete multiple items?")) :
|
|
|
|
(Text("Delete \(sidebarModel.namesOfFeedsToDelete())?")),
|
|
|
|
message: Text("Are you sure you wish to delete \(sidebarModel.namesOfFeedsToDelete())?"),
|
|
|
|
primaryButton: .destructive(Text("Delete"),
|
|
|
|
action: {
|
|
|
|
sidebarModel.deleteFromAccount.send(sidebarModel.sidebarItemToDelete!)
|
|
|
|
sidebarModel.sidebarItemToDelete = nil
|
|
|
|
sidebarModel.selectedFeedIdentifiers.removeAll()
|
|
|
|
sidebarModel.showDeleteConfirmation = false
|
|
|
|
}),
|
|
|
|
secondaryButton: .cancel(Text("Cancel"), action: {
|
|
|
|
sidebarModel.sidebarItemToDelete = nil
|
|
|
|
sidebarModel.showDeleteConfirmation = false
|
|
|
|
}))
|
|
|
|
})
|
2020-07-08 15:39:39 +02:00
|
|
|
#endif
|
2020-07-21 10:05:31 +02:00
|
|
|
|
2020-07-11 19:47:13 +02:00
|
|
|
// .onAppear {
|
|
|
|
// expandedContainers.data = expandedContainerData
|
|
|
|
// }
|
|
|
|
// .onReceive(expandedContainers.objectDidChange) {
|
|
|
|
// expandedContainerData = expandedContainers.data
|
|
|
|
// }
|
2020-07-08 15:39:39 +02:00
|
|
|
}
|
|
|
|
|
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
|
2020-07-16 04:24:22 +02:00
|
|
|
if !pulling && (scrollOffset > threshold && previousScrollOffset <= threshold) {
|
|
|
|
pulling = true
|
2020-07-25 10:40:04 +02:00
|
|
|
AccountManager.shared.refreshAll()
|
2020-07-15 23:52:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Crossing the threshold on the way UP, we end the refresh
|
2020-07-16 04:24:22 +02:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-11 19:47:13 +02:00
|
|
|
var rows: some View {
|
2020-07-23 18:53:59 +02:00
|
|
|
ForEach(sidebarItems) { sidebarItem in
|
2020-07-08 15:39:39 +02:00
|
|
|
if let containerID = sidebarItem.containerID {
|
2020-07-26 22:31:32 +02:00
|
|
|
DisclosureGroup(isExpanded: $sidebarModel.expandedContainers[containerID]) {
|
2020-07-08 15:39:39 +02:00
|
|
|
ForEach(sidebarItem.children) { sidebarItem in
|
|
|
|
if let containerID = sidebarItem.containerID {
|
2020-07-26 22:31:32 +02:00
|
|
|
DisclosureGroup(isExpanded: $sidebarModel.expandedContainers[containerID]) {
|
2020-07-08 15:39:39 +02:00
|
|
|
ForEach(sidebarItem.children) { sidebarItem in
|
2020-07-15 22:25:41 +02:00
|
|
|
SidebarItemNavigation(sidebarItem: sidebarItem)
|
2020-07-01 03:23:22 +02:00
|
|
|
}
|
2020-07-08 15:39:39 +02:00
|
|
|
} label: {
|
2020-07-15 22:25:41 +02:00
|
|
|
SidebarItemNavigation(sidebarItem: sidebarItem)
|
2020-07-01 03:23:22 +02:00
|
|
|
}
|
2020-07-08 15:39:39 +02:00
|
|
|
} else {
|
2020-07-15 22:25:41 +02:00
|
|
|
SidebarItemNavigation(sidebarItem: sidebarItem)
|
2020-07-01 03:23:22 +02:00
|
|
|
}
|
|
|
|
}
|
2020-07-08 15:39:39 +02:00
|
|
|
} label: {
|
2020-07-13 19:00:59 +02:00
|
|
|
#if os(macOS)
|
2020-07-21 10:05:31 +02:00
|
|
|
SidebarItemView(sidebarItem: sidebarItem)
|
|
|
|
.padding(.leading, 4)
|
|
|
|
.environmentObject(sidebarModel)
|
2020-07-13 19:00:59 +02:00
|
|
|
#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
|
|
|
}
|
2020-07-13 19:00:59 +02:00
|
|
|
#endif
|
2020-07-01 03:23:22 +02:00
|
|
|
}
|
2020-06-29 00:43:20 +02:00
|
|
|
}
|
2020-06-29 13:16:48 +02:00
|
|
|
}
|
2020-06-29 00:43:20 +02:00
|
|
|
}
|
2020-07-15 22:25:41 +02:00
|
|
|
|
|
|
|
struct SidebarItemNavigation: View {
|
|
|
|
|
|
|
|
@EnvironmentObject private var sidebarModel: SidebarModel
|
|
|
|
var sidebarItem: SidebarItem
|
|
|
|
|
2020-08-12 15:04:56 +02:00
|
|
|
var body: some View {
|
2020-07-15 22:25:41 +02:00
|
|
|
#if os(macOS)
|
2020-07-18 11:34:04 +02:00
|
|
|
SidebarItemView(sidebarItem: sidebarItem)
|
|
|
|
.tag(sidebarItem.feed!.feedID!)
|
2020-07-15 22:25:41 +02:00
|
|
|
#else
|
|
|
|
ZStack {
|
|
|
|
SidebarItemView(sidebarItem: sidebarItem)
|
2020-07-18 11:35:44 +02:00
|
|
|
NavigationLink(destination: TimelineContainerView(),
|
2020-07-15 22:25:41 +02:00
|
|
|
tag: sidebarItem.feed!.feedID!,
|
|
|
|
selection: $sidebarModel.selectedFeedIdentifier) {
|
|
|
|
EmptyView()
|
|
|
|
}.buttonStyle(PlainButtonStyle())
|
|
|
|
}
|
|
|
|
#endif
|
2020-07-11 19:47:13 +02:00
|
|
|
}
|
2020-07-15 22:25:41 +02:00
|
|
|
|
2020-07-11 19:47:13 +02:00
|
|
|
}
|
|
|
|
|
2020-06-28 21:21:43 +02:00
|
|
|
}
|