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")
|
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) {
|
public static func log(_ error: Error) {
|
||||||
os_log(.error, log: self.log, "%@", error.localizedDescription)
|
os_log(.error, log: self.log, "%@", error.localizedDescription)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public enum SidebarItemIdentifier: Hashable, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum RepresentedType {
|
public enum RepresentedType {
|
||||||
case webFeed, folder, pseudoFeed, account, unknown
|
case smartFeedController, webFeed, folder, pseudoFeed, account, unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SidebarItem: Identifiable {
|
struct SidebarItem: Identifiable {
|
||||||
@ -43,6 +43,8 @@ struct SidebarItem: Identifiable {
|
|||||||
|
|
||||||
var representedType: RepresentedType {
|
var representedType: RepresentedType {
|
||||||
switch type(of: represented) {
|
switch type(of: represented) {
|
||||||
|
case is SmartFeedsController.Type:
|
||||||
|
return .smartFeedController
|
||||||
case is SmartFeed.Type:
|
case is SmartFeed.Type:
|
||||||
return .pseudoFeed
|
return .pseudoFeed
|
||||||
case is UnreadFeed.Type:
|
case is UnreadFeed.Type:
|
||||||
|
@ -17,6 +17,14 @@ struct SidebarView: View {
|
|||||||
@EnvironmentObject private var sidebarModel: SidebarModel
|
@EnvironmentObject private var sidebarModel: SidebarModel
|
||||||
@State var navigate = false
|
@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 {
|
@ViewBuilder var body: some View {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
VStack {
|
VStack {
|
||||||
@ -50,10 +58,22 @@ struct SidebarView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
ZStack(alignment: .top) {
|
||||||
List {
|
List {
|
||||||
rows
|
rows
|
||||||
}
|
}
|
||||||
|
.background(RefreshFixedView())
|
||||||
.navigationTitle(Text("Feeds"))
|
.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")))
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
// .onAppear {
|
// .onAppear {
|
||||||
// expandedContainers.data = expandedContainerData
|
// 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 {
|
var rows: some View {
|
||||||
ForEach(sidebarModel.sidebarItems) { sidebarItem in
|
ForEach(sidebarModel.sidebarItems) { sidebarItem in
|
||||||
if let containerID = sidebarItem.containerID {
|
if let containerID = sidebarItem.containerID {
|
||||||
@ -84,7 +161,14 @@ struct SidebarView: View {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
SidebarItemView(sidebarItem: sidebarItem).padding(.leading, 4)
|
SidebarItemView(sidebarItem: sidebarItem).padding(.leading, 4)
|
||||||
#else
|
#else
|
||||||
|
if sidebarItem.representedType == .smartFeedController {
|
||||||
|
GeometryReader { proxy in
|
||||||
SidebarItemView(sidebarItem: sidebarItem)
|
SidebarItemView(sidebarItem: sidebarItem)
|
||||||
|
.preference(key: RefreshKeyTypes.PrefKey.self, value: [RefreshKeyTypes.PrefData(vType: .movingView, bounds: proxy.frame(in: .global))])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SidebarItemView(sidebarItem: sidebarItem)
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3144,8 +3144,8 @@
|
|||||||
51392D1A24AC19A000BE0D35 /* SidebarExpandedContainers.swift */,
|
51392D1A24AC19A000BE0D35 /* SidebarExpandedContainers.swift */,
|
||||||
51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */,
|
51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */,
|
||||||
51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */,
|
51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */,
|
||||||
51E499FC24A9137600B667CB /* SidebarModel.swift */,
|
|
||||||
5177470824B2F87600EB0F74 /* SidebarListStyleModifier.swift */,
|
5177470824B2F87600EB0F74 /* SidebarListStyleModifier.swift */,
|
||||||
|
51E499FC24A9137600B667CB /* SidebarModel.swift */,
|
||||||
17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */,
|
17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */,
|
||||||
172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */,
|
172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */,
|
||||||
51919FA524AA64B000541E64 /* SidebarView.swift */,
|
51919FA524AA64B000541E64 /* SidebarView.swift */,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user