Implement pull to refresh on iOS
This commit is contained in:
parent
2b84469fb4
commit
30d1f77405
|
@ -14,16 +14,6 @@ struct ErrorHandler {
|
|||
|
||||
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
||||
|
||||
// public static func present(_ viewController: UIViewController) -> (Error) -> () {
|
||||
// return { [weak viewController] error in
|
||||
// if UIApplication.shared.applicationState == .active {
|
||||
// viewController?.presentError(error)
|
||||
// } else {
|
||||
// ErrorHandler.log(error)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
public static func log(_ error: Error) {
|
||||
os_log(.error, log: self.log, "%@", error.localizedDescription)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public enum SidebarItemIdentifier: Hashable, Equatable {
|
|||
}
|
||||
|
||||
public enum RepresentedType {
|
||||
case webFeed, folder, pseudoFeed, account, unknown
|
||||
case smartFeedController, webFeed, folder, pseudoFeed, account, unknown
|
||||
}
|
||||
|
||||
struct SidebarItem: Identifiable {
|
||||
|
@ -43,6 +43,8 @@ struct SidebarItem: Identifiable {
|
|||
|
||||
var representedType: RepresentedType {
|
||||
switch type(of: represented) {
|
||||
case is SmartFeedsController.Type:
|
||||
return .smartFeedController
|
||||
case is SmartFeed.Type:
|
||||
return .pseudoFeed
|
||||
case is UnreadFeed.Type:
|
||||
|
|
|
@ -17,6 +17,14 @@ struct SidebarView: View {
|
|||
@EnvironmentObject private var sidebarModel: SidebarModel
|
||||
@State var navigate = false
|
||||
|
||||
@State var refreshErrorMessage = ""
|
||||
@State var showRefreshError: Bool = false
|
||||
|
||||
private let threshold: CGFloat = 80
|
||||
@State private var previousScrollOffset: CGFloat = 0
|
||||
@State private var scrollOffset: CGFloat = 0
|
||||
@State var refreshing: Bool = false
|
||||
|
||||
@ViewBuilder var body: some View {
|
||||
#if os(macOS)
|
||||
VStack {
|
||||
|
@ -50,10 +58,22 @@ struct SidebarView: View {
|
|||
}
|
||||
}
|
||||
#else
|
||||
List {
|
||||
rows
|
||||
ZStack(alignment: .top) {
|
||||
List {
|
||||
rows
|
||||
}
|
||||
.background(RefreshFixedView())
|
||||
.navigationTitle(Text("Feeds"))
|
||||
.onPreferenceChange(RefreshKeyTypes.PrefKey.self) { values in
|
||||
refreshLogic(values: values)
|
||||
}
|
||||
if refreshing {
|
||||
ProgressView().offset(y: -40)
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showRefreshError) {
|
||||
Alert(title: Text("Account Error"), message: Text(verbatim: refreshErrorMessage), dismissButton: .default(Text("OK")))
|
||||
}
|
||||
.navigationTitle(Text("Feeds"))
|
||||
#endif
|
||||
// .onAppear {
|
||||
// expandedContainers.data = expandedContainerData
|
||||
|
@ -63,6 +83,63 @@ struct SidebarView: View {
|
|||
// }
|
||||
}
|
||||
|
||||
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 !refreshing && (scrollOffset > threshold && previousScrollOffset <= threshold) {
|
||||
refreshing = true
|
||||
AccountManager.shared.refreshAll(errorHandler: handleRefreshError)
|
||||
}
|
||||
|
||||
// Crossing the threshold on the way UP, we end the refresh
|
||||
if refreshing && previousScrollOffset > threshold && scrollOffset <= threshold {
|
||||
refreshing = false
|
||||
}
|
||||
|
||||
// Update last scroll offset
|
||||
self.previousScrollOffset = self.scrollOffset
|
||||
}
|
||||
}
|
||||
|
||||
func handleRefreshError(_ error: Error) {
|
||||
refreshErrorMessage = error.localizedDescription
|
||||
showRefreshError = true
|
||||
}
|
||||
|
||||
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(sidebarModel.sidebarItems) { sidebarItem in
|
||||
if let containerID = sidebarItem.containerID {
|
||||
|
@ -84,7 +161,14 @@ struct SidebarView: View {
|
|||
#if os(macOS)
|
||||
SidebarItemView(sidebarItem: sidebarItem).padding(.leading, 4)
|
||||
#else
|
||||
SidebarItemView(sidebarItem: sidebarItem)
|
||||
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))])
|
||||
}
|
||||
} else {
|
||||
SidebarItemView(sidebarItem: sidebarItem)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3144,8 +3144,8 @@
|
|||
51392D1A24AC19A000BE0D35 /* SidebarExpandedContainers.swift */,
|
||||
51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */,
|
||||
51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */,
|
||||
51E499FC24A9137600B667CB /* SidebarModel.swift */,
|
||||
5177470824B2F87600EB0F74 /* SidebarListStyleModifier.swift */,
|
||||
51E499FC24A9137600B667CB /* SidebarModel.swift */,
|
||||
17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */,
|
||||
172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */,
|
||||
51919FA524AA64B000541E64 /* SidebarView.swift */,
|
||||
|
|
Loading…
Reference in New Issue