From fe2de1a4060d5a037e755d7cc0bdd683ff248b11 Mon Sep 17 00:00:00 2001 From: Hesham Salman Date: Wed, 14 Feb 2024 10:13:53 -0500 Subject: [PATCH] Additional conveniences for offset pagination --- .../GraphQLQueryPager+Convenience.swift | 282 +++++++++++++++++- .../OffsetBasedPagination/Bidirectional.swift | 13 + .../OffsetBasedPagination/ForwardOffset.swift | 13 + .../OffsetPagination.swift | 11 +- .../OffsetBasedPagination/ReverseOffset.swift | 13 + 5 files changed, 306 insertions(+), 26 deletions(-) create mode 100644 apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/Bidirectional.swift create mode 100644 apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/ForwardOffset.swift create mode 100644 apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/ReverseOffset.swift diff --git a/apollo-ios-pagination/Sources/ApolloPagination/GraphQLQueryPager+Convenience.swift b/apollo-ios-pagination/Sources/ApolloPagination/GraphQLQueryPager+Convenience.swift index 3c1ac38b4..c60c4d064 100644 --- a/apollo-ios-pagination/Sources/ApolloPagination/GraphQLQueryPager+Convenience.swift +++ b/apollo-ios-pagination/Sources/ApolloPagination/GraphQLQueryPager+Convenience.swift @@ -11,8 +11,8 @@ public extension GraphQLQueryPager { static func makeForwardOffsetQueryPager( client: ApolloClientProtocol, watcherDispatchQueue: DispatchQueue = .main, - queryProvider: @escaping (OffsetPagination?) -> InitialQuery, - extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination + queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward ) -> GraphQLQueryPager where Model == PaginationOutput { GraphQLQueryPager(pager: GraphQLQueryPagerCoordinator( client: client, @@ -26,11 +26,33 @@ public extension GraphQLQueryPager { )) } + static func makeForwardOffsetQueryPager( + client: ApolloClientProtocol, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward, + transform: @escaping ([InitialQuery.Data], InitialQuery.Data, [InitialQuery.Data]) throws -> Model + ) -> GraphQLQueryPager { + GraphQLQueryPager( + pager: GraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(nil), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + guard direction == .next else { return nil } + return queryProvider(page) + } + ), + transform: transform + ) + } + static func makeForwardOffsetQueryPager( client: ApolloClientProtocol, watcherDispatchQueue: DispatchQueue = .main, - queryProvider: @escaping (OffsetPagination?) -> InitialQuery, - extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination, + queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward, transform: @escaping (InitialQuery.Data) throws -> Model ) -> GraphQLQueryPager where Model: RangeReplaceableCollection, T == Model.Element { GraphQLQueryPager( @@ -52,8 +74,8 @@ public extension GraphQLQueryPager { static func makeReverseOffsetQueryPager( client: ApolloClientProtocol, watcherDispatchQueue: DispatchQueue = .main, - queryProvider: @escaping (OffsetPagination?) -> InitialQuery, - extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination + queryProvider: @escaping (OffsetPagination.Reverse?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Reverse ) -> GraphQLQueryPager where Model == PaginationOutput { GraphQLQueryPager(pager: GraphQLQueryPagerCoordinator( client: client, @@ -67,11 +89,33 @@ public extension GraphQLQueryPager { )) } + static func makeReverseOffsetQueryPager( + client: ApolloClientProtocol, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Reverse?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Reverse, + transform: @escaping ([InitialQuery.Data], InitialQuery.Data, [InitialQuery.Data]) throws -> Model + ) -> GraphQLQueryPager { + GraphQLQueryPager( + pager: GraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(nil), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + guard direction == .previous else { return nil } + return queryProvider(page) + } + ), + transform: transform + ) + } + static func makeReverseOffsetQueryPager( client: ApolloClientProtocol, watcherDispatchQueue: DispatchQueue = .main, - queryProvider: @escaping (OffsetPagination?) -> InitialQuery, - extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination, + queryProvider: @escaping (OffsetPagination.Reverse?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Reverse, transform: @escaping (InitialQuery.Data) throws -> Model ) -> GraphQLQueryPager where Model: RangeReplaceableCollection, T == Model.Element { GraphQLQueryPager( @@ -90,6 +134,87 @@ public extension GraphQLQueryPager { ) } + static func makeBidirectionalOffsetQueryPager( + client: ApolloClientProtocol, + start: OffsetPagination.Bidirectional?, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + previousQueryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Bidirectional + ) -> GraphQLQueryPager where Model == PaginationOutput { + GraphQLQueryPager(pager: GraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(start), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + switch direction { + case .next: + return queryProvider(page) + case .previous: + return previousQueryProvider(page) + } + } + )) + } + + static func makeBidirectionalOffsetQueryPager( + client: ApolloClientProtocol, + start: OffsetPagination.Bidirectional?, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + previousQueryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Bidirectional, + transform: @escaping ([InitialQuery.Data], InitialQuery.Data, [InitialQuery.Data]) throws -> Model + ) -> GraphQLQueryPager { + GraphQLQueryPager( + pager: GraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(start), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + switch direction { + case .next: + return queryProvider(page) + case .previous: + return previousQueryProvider(page) + } + } + ), + transform: transform + ) + } + + static func makeBidirectionalOffsetQueryPager( + client: ApolloClientProtocol, + start: OffsetPagination.Bidirectional?, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + previousQueryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Bidirectional, + transform: @escaping (InitialQuery.Data) throws -> Model + ) -> GraphQLQueryPager where Model: RangeReplaceableCollection, T == Model.Element { + GraphQLQueryPager( + pager: GraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(start), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + switch direction { + case .next: + return queryProvider(page) + case .previous: + return previousQueryProvider(page) + } + } + ), + initialTransform: transform, + pageTransform: transform + ) + } + // MARK: CursorBasedPagination.Forward Initializers /// This convenience function creates an `GraphQLQueryPager` that paginates forward with only one query and has an output type of `Result<(PaginationOutput, UpdateSource), Error>`. @@ -522,8 +647,8 @@ public extension AsyncGraphQLQueryPager { static func makeForwardOffsetQueryPager( client: ApolloClientProtocol, watcherDispatchQueue: DispatchQueue = .main, - queryProvider: @escaping (OffsetPagination?) -> InitialQuery, - extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination + queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward ) async -> AsyncGraphQLQueryPager where Model == PaginationOutput { await AsyncGraphQLQueryPager(pager: AsyncGraphQLQueryPagerCoordinator( client: client, @@ -537,11 +662,33 @@ public extension AsyncGraphQLQueryPager { )) } + static func makeForwardOffsetQueryPager( + client: ApolloClientProtocol, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward, + transform: @escaping ([InitialQuery.Data], InitialQuery.Data, [InitialQuery.Data]) throws -> Model + ) async -> AsyncGraphQLQueryPager { + await AsyncGraphQLQueryPager( + pager: AsyncGraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(nil), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + guard direction == .next else { return nil } + return queryProvider(page) + } + ), + transform: transform + ) + } + static func makeForwardOffsetQueryPager( client: ApolloClientProtocol, watcherDispatchQueue: DispatchQueue = .main, - queryProvider: @escaping (OffsetPagination?) -> InitialQuery, - extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination, + queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward, transform: @escaping (InitialQuery.Data) throws -> Model ) async -> AsyncGraphQLQueryPager where Model: RangeReplaceableCollection, T == Model.Element { await AsyncGraphQLQueryPager( @@ -563,8 +710,8 @@ public extension AsyncGraphQLQueryPager { static func makeReverseOffsetQueryPager( client: ApolloClientProtocol, watcherDispatchQueue: DispatchQueue = .main, - queryProvider: @escaping (OffsetPagination?) -> InitialQuery, - extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination + queryProvider: @escaping (OffsetPagination.Reverse?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Reverse ) async -> AsyncGraphQLQueryPager where Model == PaginationOutput { await AsyncGraphQLQueryPager(pager: AsyncGraphQLQueryPagerCoordinator( client: client, @@ -578,11 +725,33 @@ public extension AsyncGraphQLQueryPager { )) } + static func makeReverseOffsetQueryPager( + client: ApolloClientProtocol, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Reverse?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Reverse, + transform: @escaping ([InitialQuery.Data], InitialQuery.Data, [InitialQuery.Data]) throws -> Model + ) async -> AsyncGraphQLQueryPager { + await AsyncGraphQLQueryPager( + pager: AsyncGraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(nil), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + guard direction == .previous else { return nil } + return queryProvider(page) + } + ), + transform: transform + ) + } + static func makeReverseOffsetQueryPager( client: ApolloClientProtocol, watcherDispatchQueue: DispatchQueue = .main, - queryProvider: @escaping (OffsetPagination?) -> InitialQuery, - extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination, + queryProvider: @escaping (OffsetPagination.Reverse?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Reverse, transform: @escaping (InitialQuery.Data) throws -> Model ) async -> AsyncGraphQLQueryPager where Model: RangeReplaceableCollection, T == Model.Element { await AsyncGraphQLQueryPager( @@ -601,6 +770,87 @@ public extension AsyncGraphQLQueryPager { ) } + static func makeBidirectionalOffsetQueryPager( + client: ApolloClientProtocol, + start: OffsetPagination.Bidirectional?, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + previousQueryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Bidirectional + ) async -> AsyncGraphQLQueryPager where Model == PaginationOutput { + await AsyncGraphQLQueryPager(pager: AsyncGraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(start), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + switch direction { + case .next: + return queryProvider(page) + case .previous: + return previousQueryProvider(page) + } + } + )) + } + + static func makeBidirectionalOffsetQueryPager( + client: ApolloClientProtocol, + start: OffsetPagination.Bidirectional?, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + previousQueryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Bidirectional, + transform: @escaping ([InitialQuery.Data], InitialQuery.Data, [InitialQuery.Data]) throws -> Model + ) async -> AsyncGraphQLQueryPager { + await AsyncGraphQLQueryPager( + pager: AsyncGraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(start), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + switch direction { + case .next: + return queryProvider(page) + case .previous: + return previousQueryProvider(page) + } + } + ), + transform: transform + ) + } + + static func makeBidirectionalOffsetQueryPager( + client: ApolloClientProtocol, + start: OffsetPagination.Bidirectional?, + watcherDispatchQueue: DispatchQueue = .main, + queryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + previousQueryProvider: @escaping (OffsetPagination.Bidirectional?) -> InitialQuery, + extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Bidirectional, + transform: @escaping (InitialQuery.Data) throws -> Model + ) async -> AsyncGraphQLQueryPager where Model: RangeReplaceableCollection, T == Model.Element { + await AsyncGraphQLQueryPager( + pager: AsyncGraphQLQueryPagerCoordinator( + client: client, + initialQuery: queryProvider(start), + watcherDispatchQueue: watcherDispatchQueue, + extractPageInfo: pageExtraction(transform: extractPageInfo), + pageResolver: { page, direction in + switch direction { + case .next: + return queryProvider(page) + case .previous: + return previousQueryProvider(page) + } + } + ), + initialTransform: transform, + pageTransform: transform + ) + } + // MARK: CursorBasedPagination.Forward Initializers /// This convenience function creates an `AsyncGraphQLQueryPager` that paginates forward with only one query and has an output type of `Result<(PaginationOutput, UpdateSource), Error>`. diff --git a/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/Bidirectional.swift b/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/Bidirectional.swift new file mode 100644 index 000000000..443ef6e59 --- /dev/null +++ b/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/Bidirectional.swift @@ -0,0 +1,13 @@ +extension OffsetPagination { + public struct Bidirectional: PaginationInfo, Hashable { + public let offset: Int + public var canLoadNext: Bool + public let canLoadPrevious: Bool + + public init(offset: Int, canLoadNext: Bool, canLoadPrevious: Bool) { + self.offset = offset + self.canLoadNext = canLoadNext + self.canLoadPrevious = canLoadPrevious + } + } +} diff --git a/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/ForwardOffset.swift b/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/ForwardOffset.swift new file mode 100644 index 000000000..cd193d91d --- /dev/null +++ b/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/ForwardOffset.swift @@ -0,0 +1,13 @@ +extension OffsetPagination { + public struct Forward: PaginationInfo, Hashable { + public let offset: Int + public let canLoadNext: Bool + public var canLoadPrevious: Bool { false } + + public init(offset: Int, canLoadNext: Bool) { + self.offset = offset + self.canLoadNext = canLoadNext + } + } + +} diff --git a/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/OffsetPagination.swift b/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/OffsetPagination.swift index 0e7f35bea..c987fe5c0 100644 --- a/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/OffsetPagination.swift +++ b/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/OffsetPagination.swift @@ -1,10 +1 @@ -public struct OffsetPagination: PaginationInfo, Hashable { - public let offset: Int - public let canLoadNext: Bool - public var canLoadPrevious: Bool { false } - - public init(offset: Int, canLoadNext: Bool) { - self.offset = offset - self.canLoadNext = canLoadNext - } -} +public enum OffsetPagination { } diff --git a/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/ReverseOffset.swift b/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/ReverseOffset.swift new file mode 100644 index 000000000..0ddc07700 --- /dev/null +++ b/apollo-ios-pagination/Sources/ApolloPagination/OffsetBasedPagination/ReverseOffset.swift @@ -0,0 +1,13 @@ +extension OffsetPagination { + public struct Reverse: PaginationInfo, Hashable { + public let offset: Int + public var canLoadNext: Bool { false } + public let canLoadPrevious: Bool + + public init(offset: Int, canLoadPrevious: Bool) { + self.offset = offset + self.canLoadPrevious = canLoadPrevious + } + } + +}