Skip to content

Commit

Permalink
feat(pager): add custom delay between pages
Browse files Browse the repository at this point in the history
  • Loading branch information
sbertix committed May 22, 2021
1 parent 130a78d commit 52fbbd8
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ public struct PagerProviderInput<Offset> {
public let count: Int
/// The actual offset.
public let offset: Offset
/// The delay.
public let delay: TimeInterval

/// Init.
///
/// - parameters:
/// - count: A valid `Int`.
/// - offset: A valid `Offset`.
public init(count: Int, offset: Offset) {
/// - delay: A valid `TimeInterval`.
public init(count: Int, offset: Offset, delay: TimeInterval) {
self.count = count
self.offset = offset
self.delay = delay
}
}

Expand Down
44 changes: 27 additions & 17 deletions Sources/ComposableRequest/Providers/Pager/PagerProviderType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,34 @@ public extension PagerProviderType {
/// - parameters:
/// - count: A valid `Int`.
/// - offset: A valid `Offset`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - returns: Some `Content`.
func pages(_ count: Int, offset: Offset) -> Output {
Self.generate(self, from: .init(count: count, offset: offset))
func pages(_ count: Int, offset: Offset, delay: TimeInterval = 0) -> Output {
Self.generate(self, from: .init(count: count, offset: offset, delay: delay))
}
}

public extension PagerProviderType where Offset == Void {
/// Authenticate.
///
/// - parameter count: A valid `Int`.
/// - parameters:
/// - count: A valid `Int`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - returns: Some `Content`.
func pages(_ count: Int) -> Output {
self.pages(count, offset: ())
func pages(_ count: Int, delay: TimeInterval = 0) -> Output {
self.pages(count, offset: (), delay: delay)
}
}

public extension PagerProviderType where Offset: ComposableOptionalType {
/// Authenticate.
///
/// - parameter count: A valid `Int`.
/// - parameters:
/// - count: A valid `Int`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - returns: Some `Content`.
func pages(_ count: Int) -> Output {
self.pages(count, offset: .composableNone)
func pages(_ count: Int, delay: TimeInterval = 0) -> Output {
self.pages(count, offset: .composableNone, delay: delay)
}
}

Expand All @@ -52,9 +57,10 @@ public extension PagerProviderType where Offset: Ranked {
/// - count: A valid `Int`.
/// - offset: A valid `Offset`.
/// - rank: A valid `Rank`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - returns: Some `Content`.
func pages(_ count: Int, offset: Offset.Offset, rank: Offset.Rank) -> Output {
self.pages(count, offset: .init(offset: offset, rank: rank))
func pages(_ count: Int, offset: Offset.Offset, rank: Offset.Rank, delay: TimeInterval = 0) -> Output {
self.pages(count, offset: .init(offset: offset, rank: rank), delay: delay)
}
}

Expand All @@ -64,9 +70,10 @@ public extension PagerProviderType where Offset: Ranked, Offset.Offset: Composab
/// - parameters:
/// - count: A valid `Int`.
/// - rank: A valid `Rank`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - returns: Some `Content`.
func pages(_ count: Int, rank: Offset.Rank) -> Output {
self.pages(count, offset: .init(offset: .composableNone, rank: rank))
func pages(_ count: Int, rank: Offset.Rank, delay: TimeInterval = 0) -> Output {
self.pages(count, offset: .init(offset: .composableNone, rank: rank), delay: delay)
}
}

Expand All @@ -76,19 +83,22 @@ public extension PagerProviderType where Offset: Ranked, Offset.Rank: Composable
/// - parameters:
/// - count: A valid `Int`.
/// - offset: A valid `Offset`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - returns: Some `Content`.
func pages(_ count: Int, offset: Offset.Offset) -> Output {
self.pages(count, offset: .init(offset: offset, rank: .composableNone))
func pages(_ count: Int, offset: Offset.Offset, delay: TimeInterval = 0) -> Output {
self.pages(count, offset: .init(offset: offset, rank: .composableNone), delay: delay)
}
}

public extension PagerProviderType
where Offset: Ranked, Offset.Offset: ComposableOptionalType, Offset.Rank: ComposableOptionalType {
/// Set up pagination.
///
/// - parameter count: A valid `Int`.
/// - parameters:
/// - count: A valid `Int`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - returns: Some `Content`.
func pages(_ count: Int) -> Output {
self.pages(count, offset: .init(offset: .composableNone, rank: .composableNone))
func pages(_ count: Int, delay: TimeInterval = 0) -> Output {
self.pages(count, offset: .init(offset: .composableNone, rank: .composableNone), delay: delay)
}
}
31 changes: 21 additions & 10 deletions Sources/ComposableRequest/Publishers/Pager/Pager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public extension Publishers {
private let count: Int
/// The current offset.
private let offset: Offset
/// The delay between calls.
private let delay: TimeInterval
/// The iteration generator.
private let generator: (_ offset: Offset) -> Iteration

Expand All @@ -28,12 +30,15 @@ public extension Publishers {
/// - parameters:
/// - count: A valid `Int`. Defaults to `.max`.
/// - offset: A valid `Offset`.
/// - delay: A valid `TimeInterval`.
/// - generator: A valid generator.
public init(_ count: Int = .max,
offset: Offset,
delay: TimeInterval,
generator: @escaping (_ offset: Offset) -> Iteration) {
self.count = count
self.offset = offset
self.delay = delay
self.generator = generator
}

Expand All @@ -44,7 +49,7 @@ public extension Publishers {
/// - generator: A valid generator.
public init(_ pages: PagerProviderInput<Offset>,
generator: @escaping (_ offset: Offset) -> Iteration) {
self.init(pages.count, offset: pages.offset, generator: generator)
self.init(pages.count, offset: pages.offset, delay: pages.delay, generator: generator)
}

/// Receive a subscriber.
Expand Down Expand Up @@ -73,7 +78,8 @@ public extension Publishers {
return current.eraseToAnyPublisher()
case .load(let next):
return current
.append(Pager(count - 1, offset: next, generator: generator))
.append(Deferred { Pager(count - 1, offset: next, delay: delay, generator: generator) }
.delay(for: .seconds(count - 1 > 0 ? delay : 0), scheduler: RunLoop.main))
.eraseToAnyPublisher()
}
}
Expand All @@ -94,9 +100,10 @@ public extension Pager where Offset == Void {
///
/// - parameters:
/// - count: A valid `Int`. Defaults to `.max`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - generator: A valid generator.
init(_ count: Int = .max, generator: @escaping () -> Iteration) {
self.init(count, offset: ()) { _ in generator() }
init(_ count: Int = .max, delay: TimeInterval = 0, generator: @escaping () -> Iteration) {
self.init(count, offset: (), delay: delay) { _ in generator() }
}

/// Init.
Expand All @@ -112,9 +119,10 @@ public extension Pager where Offset == Void {
///
/// - parameters:
/// - count: A valid `Int`. Defaults to `.max`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - generator: A valid generator.
init(_ count: Int = .max, generator: @escaping () -> Stream) {
self.init(count, offset: ()) { _ in generator().iterate() }
init(_ count: Int = .max, delay: TimeInterval = 0, generator: @escaping () -> Stream) {
self.init(count, offset: (), delay: delay) { _ in generator().iterate() }
}

/// Init.
Expand All @@ -132,9 +140,10 @@ public extension Pager where Offset: ComposableOptionalType {
///
/// - parameters:
/// - count: A valid `Int`. Defaults to `.max`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - generator: A valid generator.
init(_ count: Int = .max, generator: @escaping (_ offset: Offset) -> Iteration) {
self.init(count, offset: .composableNone, generator: generator)
init(_ count: Int = .max, delay: TimeInterval = 0, generator: @escaping (_ offset: Offset) -> Iteration) {
self.init(count, offset: .composableNone, delay: delay, generator: generator)
}
}

Expand All @@ -144,11 +153,13 @@ public extension Pager where Offset: Ranked {
/// - parameters:
/// - count: A valid `Int`. Defaults to `.max`.
/// - offset: A valid `Offset`.
/// - delay: A valid `TimeInterval`. Defaults to `0`.
/// - generator: A valid generator.
init(_ count: Int = .max,
offset: Offset,
delay: TimeInterval = 0,
generator: @escaping (_ offset: Offset.Offset) -> Pager<Offset.Offset, Stream>.Iteration) {
self.init(count, offset: offset) { offset -> Iteration in
self.init(count, offset: offset, delay: delay) { offset -> Iteration in
let iteration = generator(offset.offset)
return .init(stream: iteration.stream) {
switch iteration.offset($0) {
Expand All @@ -168,6 +179,6 @@ public extension Pager where Offset: Ranked {
/// - generator: A valid generator.
init(_ pages: PagerProviderInput<Offset>,
generator: @escaping (_ offset: Offset.Offset) -> Pager<Offset.Offset, Stream>.Iteration) {
self.init(pages.count, offset: pages.offset, generator: generator)
self.init(pages.count, offset: pages.offset, delay: pages.delay, generator: generator)
}
}
13 changes: 11 additions & 2 deletions Tests/ComposableRequestTests/ObservableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ internal final class ObservableTests: XCTestCase {
/// Test a paginated fetch request.
func testPagination() {
let languages = ["en", "it", "de", "fr"]
let expectations = languages.map(XCTestExpectation.init) + [XCTestExpectation(description: "completion")]
let expectations = [languages.map(XCTestExpectation.init),
[XCTestExpectation(description: "delay"), XCTestExpectation(description: "completion")]]
.reduce(into: []) { $0.append(contentsOf: $1) }
let offset = Reference(0)
let delayed = Reference(false)
// Prepare the provider.
PagerProvider { pages in
// Actually paginate futures.
Expand All @@ -111,12 +114,13 @@ internal final class ObservableTests: XCTestCase {
.iterateFirst(stoppingAt: offset) { .load($0 + 1) }
}
}
.pages(languages.count, offset: 0)
.pages(languages.count, offset: 0, delay: 2)
.receive(on: RunLoop.main)
.assertMainThread()
.sink(
receiveCompletion: {
if case .failure(let error) = $0 { XCTFail(error.localizedDescription) }
XCTAssert(delayed.value, "Pagination did not wait.")
expectations.last?.fulfill()
},
receiveValue: {
Expand All @@ -126,6 +130,11 @@ internal final class ObservableTests: XCTestCase {
}
)
.store(in: &bin)
// Make sure you delay before it's completed.
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
delayed.value = true
expectations[4].fulfill()
}
wait(for: expectations, timeout: 30)
}

Expand Down

0 comments on commit 52fbbd8

Please sign in to comment.