Skip to content

Commit 66d74ee

Browse files
authored
Up Next reorder delay (#3836)
2 parents d401168 + 61601bb commit 66d74ee

File tree

5 files changed

+83
-12
lines changed

5 files changed

+83
-12
lines changed

PocketCastsTests/PlaybackQueueTests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,36 @@ final class PlaybackQueueTests: XCTestCase {
3434
"Should not include stale episode UUID in replacement list")
3535
}
3636

37+
func testRecentUserInteractionReturnsFalseWhenNoPreviousInteraction() {
38+
let playbackQueue = PlaybackQueue()
39+
40+
XCTAssertFalse(playbackQueue.recentUserInteraction(now: Date(timeIntervalSince1970: 15)))
41+
}
42+
43+
func testRecentUserInteractionReturnsTrueWithinGracePeriod() {
44+
let playbackQueue = PlaybackQueue()
45+
let interactionTime = Date(timeIntervalSince1970: 1_000)
46+
playbackQueue.recordUpNextUserInteraction(at: interactionTime)
47+
48+
XCTAssertTrue(playbackQueue.recentUserInteraction(now: interactionTime.addingTimeInterval(3)))
49+
}
50+
51+
func testRecentUserInteractionReturnsFalseAtGracePeriodBoundary() {
52+
let playbackQueue = PlaybackQueue()
53+
let interactionTime = Date(timeIntervalSince1970: 1_000)
54+
playbackQueue.recordUpNextUserInteraction(at: interactionTime)
55+
56+
XCTAssertFalse(playbackQueue.recentUserInteraction(now: interactionTime.addingTimeInterval(10)))
57+
}
58+
59+
func testRecentUserInteractionReturnsFalseOutsideGracePeriod() {
60+
let playbackQueue = PlaybackQueue()
61+
let interactionTime = Date(timeIntervalSince1970: 1_000)
62+
playbackQueue.recordUpNextUserInteraction(at: interactionTime)
63+
64+
XCTAssertFalse(playbackQueue.recentUserInteraction(now: interactionTime.addingTimeInterval(11)))
65+
}
66+
3767
override func tearDown() {
3868
featureFlagMock.reset()
3969
}

podcasts/PlaybackManager.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ class PlaybackManager: ServerPlaybackDelegate {
154154
player?.futureBufferAvailable() ?? 0
155155
}
156156

157+
func recordUpNextUserInteraction() {
158+
queue.recordUpNextUserInteraction()
159+
}
160+
157161
func load(episode: BaseEpisode, autoPlay: Bool, overrideUpNext: Bool, saveCurrentEpisode: Bool = true, completion: (() -> Void)? = nil) {
158162
FileLog.shared.addMessage("Loading \(episode.displayableTitle()) with UUID \(episode.uuid) autoPlay \(autoPlay) overrideUpNext: \(overrideUpNext)")
159163

podcasts/PlaybackQueue.swift

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,28 @@ class PlaybackQueue: NSObject {
88
private var topEpisode: BaseEpisode?
99

1010
private let syncTimerDelay: TimeInterval = 5
11+
private let interactionGracePeriod: TimeInterval = 10
1112
private var syncTimer: Timer?
13+
private var lastUserInteractionTime: Date?
14+
15+
// MARK: - User Interaction Tracking
16+
17+
func recordUpNextUserInteraction(at date: Date = Date()) {
18+
lastUserInteractionTime = date
19+
}
20+
21+
func recentUserInteraction(now: Date = Date()) -> Bool {
22+
remainingInteractionDelay(now: now) != nil
23+
}
24+
25+
private func remainingInteractionDelay(now: Date = Date()) -> TimeInterval? {
26+
guard let lastUserInteractionTime else { return nil }
27+
28+
let elapsed = now.timeIntervalSince(lastUserInteractionTime)
29+
let remaining = interactionGracePeriod - elapsed
30+
31+
return remaining > 0 ? remaining : nil
32+
}
1233

1334
// MARK: - Editing
1435

@@ -400,27 +421,40 @@ class PlaybackQueue: NSObject {
400421

401422
// MARK: - Sync Timer
402423

403-
private func startSyncTimer() {
424+
private func cancelSyncTimer() {
425+
syncTimer?.invalidate()
426+
syncTimer = nil
427+
}
428+
429+
private func startSyncTimer(after delay: TimeInterval? = nil) {
404430
cancelSyncTimer()
431+
scheduleSyncTimer(after: delay ?? syncTimerDelay)
432+
}
433+
434+
private func scheduleSyncTimer(after delay: TimeInterval) {
435+
let scheduleTimer: () -> Void = { [weak self] in
436+
guard let self else { return }
437+
438+
self.syncTimer = Timer.scheduledTimer(timeInterval: delay, target: self, selector: #selector(self.syncTimerFired), userInfo: nil, repeats: false)
439+
}
405440

406441
// schedule the timer on a thread that has a run loop, the main thread being a good option
407442
if Thread.isMainThread {
408-
syncTimer = Timer.scheduledTimer(timeInterval: syncTimerDelay, target: self, selector: #selector(syncTimerFired), userInfo: nil, repeats: false)
443+
scheduleTimer()
409444
} else {
410-
DispatchQueue.main.sync { [weak self] () in
411-
guard let self else { return }
412-
413-
self.syncTimer = Timer.scheduledTimer(timeInterval: self.syncTimerDelay, target: self, selector: #selector(self.syncTimerFired), userInfo: nil, repeats: false)
445+
DispatchQueue.main.sync {
446+
scheduleTimer()
414447
}
415448
}
416449
}
417450

418-
private func cancelSyncTimer() {
419-
syncTimer?.invalidate()
420-
syncTimer = nil
421-
}
422-
423451
@objc private func syncTimerFired() {
452+
if let remainingDelay = remainingInteractionDelay() {
453+
FileLog.shared.addMessage("PlaybackQueue: Delaying Up Next sync for \(Int(remainingDelay.rounded(.up))) seconds due to recent interaction")
454+
startSyncTimer(after: remainingDelay)
455+
return
456+
}
457+
424458
RefreshManager.shared.syncUpNext()
425459
}
426460
}

podcasts/UpNextViewController+Table.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,9 @@ extension UpNextViewController: UITableViewDelegate, UITableViewDataSource {
212212
func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
213213
let toSection = tableData[proposedDestinationIndexPath.section]
214214

215-
if toSection == .upNextSection { return proposedDestinationIndexPath }
215+
if toSection == .upNextSection {
216+
return proposedDestinationIndexPath
217+
}
216218

217219
return IndexPath(row: 0, section: sourceIndexPath.section)
218220
}

podcasts/UpNextViewController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ class UpNextViewController: UIViewController, UIGestureRecognizerDelegate {
401401
extension UpNextViewController {
402402
@objc func reorderingDidBegin() {
403403
isReorderInProgress = true
404+
PlaybackManager.shared.recordUpNextUserInteraction()
404405
}
405406

406407
@objc func reorderingDidEnd() {

0 commit comments

Comments
 (0)