Implement pull to refresh on iOS

This commit is contained in:
Maurice Parker 2020-07-15 16:52:57 -05:00
parent 2b84469fb4
commit 30d1f77405
4 changed files with 92 additions and 16 deletions

View File

@ -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)
}

View File

@ -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:

View File

@ -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
}
}

View File

@ -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 */,