-
Notifications
You must be signed in to change notification settings - Fork 272
Fix dragging Up Next and syncing at the same time #4862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a race condition where Up Next list rearrangement could be interrupted by background sync operations that occur every 5 seconds. The solution introduces user interaction tracking to skip sync operations during and shortly after user interactions like dragging to rearrange episodes.
Key Changes:
- Migrated from RxJava to Kotlin Coroutines/Flow for Up Next sync scheduling
- Added user interaction tracking with a 10-second grace period to prevent sync during drag operations
- Converted blocking database operations to suspend functions for better coroutine integration
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| UpNextChangeDao.kt | Converted findAllBlocking() to suspend findAll() function |
| UpNextSyncWorker.kt | Updated to use the new suspend findAll() function |
| UpNextQueue.kt | Added recordUpNextUserInteraction() interface method |
| UpNextQueueImpl.kt | Migrated RxJava sync scheduling to Coroutines Flow; added user interaction tracking and 10-second grace period logic |
| PlaybackManager.kt | Added pass-through method to record user interactions |
| PlayerViewModel.kt | Added method to record user interactions from UI layer |
| UpNextFragment.kt | Calls recordUpNextUserInteraction() on episode drag to prevent sync conflicts |
...tories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/UpNextQueueImpl.kt
Outdated
Show resolved
Hide resolved
...tories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/UpNextQueueImpl.kt
Outdated
Show resolved
Hide resolved
...ures/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/viewmodel/PlayerViewModel.kt
Outdated
Show resolved
Hide resolved
| fun recentUserInteraction(): Boolean { | ||
| val now = System.currentTimeMillis() | ||
| val timeSinceInteraction = (now - lastUserInteractionTime).milliseconds | ||
| val recentInteraction = timeSinceInteraction < INTERACTION_GRACE_PERIOD | ||
| if (recentInteraction) { | ||
| Timber.i("Skipping sync - user interacted ${timeSinceInteraction.inWholeSeconds} secs ago") | ||
| } | ||
| return recentInteraction | ||
| } |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new user interaction tracking behavior lacks test coverage. Consider adding tests to verify that the sync is properly skipped during the grace period after user interaction and that the sync resumes after the grace period expires. This would help ensure the fix works correctly and prevent regressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added some tests.
|
Their is also a bug where if you move episodes when stuff is being added to upnext, the new episodes won't be added if you end up moving a episode before they are added to upnext, something I've just noticed over the years |
|
@CookieyedCodes thanks for letting me know. I'm trying to address Up Next issues. Would the episodes be added by features like "Auto add to Up Next"? I have to figure out how to repeat the issue to make sure I can fix it properly. |
|
Yup it would be using auto add to upnext, this is certainly more relatable then my other issue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
...es/src/test/java/au/com/shiftyjelly/pocketcasts/repositories/playback/UpNextQueueImplTest.kt
Outdated
Show resolved
Hide resolved
sztomek
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be wrong, but it doesn't seem to work for me:
Screen_recording_20251219_091658.mp4
logs:
2025-12-19 09:17:04.987 6216-6385 BgTask au....shiftyjelly.pocketcasts.debug I BgTask: UpNextSyncWorker - scheduled
2025-12-19 09:17:05.022 6216-6216 WM-WorkerWrapper au....shiftyjelly.pocketcasts.debug D Starting work for au.com.shiftyjelly.pocketcasts.repositories.sync.UpNextSyncWorker
2025-12-19 09:17:05.022 6216-6374 BgTask au....shiftyjelly.pocketcasts.debug I BgTask: UpNextSyncWorker - started
2025-12-19 09:17:09.683 6216-6372 BgTask au....shiftyjelly.pocketcasts.debug I BgTask: UpNextSyncWorker - finished - 4661 ms
2025-12-19 09:17:09.687 6216-6325 WM-WorkerWrapper au....shiftyjelly.pocketcasts.debug I Worker result SUCCESS for Work [ id=87f3fb77-7282-4b1c-b8ec-b07ff70cb122, tags={ au.com.shiftyjelly.pocketcasts.repositories.sync.UpNextSyncWorker,pocket_casts_up_next_sync_worker_tag } ]
| ) : UpNextQueue, | ||
| CoroutineScope { | ||
|
|
||
| companion object { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 alternative, you could define private companion object and let the consts be val XXXX = Y.seconds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know about private companion object, thanks. I might do it next time.
| private val upNextChangeDao = appDatabase.upNextChangeDao() | ||
| private val podcastDao = appDatabase.podcastDao() | ||
|
|
||
| @Volatile private var lastUserInteractionTime: Long = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 lastUserInteractionTimeMs would be more precise. Or use a Clock instance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks I have had added Ms
|
@geekygecko could you please review my comments and update the PR? |
3b847a4 to
07effe1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
|
|
||
| private var userRearrangingFrom: Int? = null | ||
| private var userDraggingStart: Int? = null | ||
| private var userDraggingStartMs: Int? = null |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable name userDraggingStartMs is misleading. The "Ms" suffix suggests it stores milliseconds, but it actually stores an adapter position (integer index). Consider renaming it to userDraggingStartPosition for clarity.
| private var userDraggingStartMs: Int? = null | |
| private var userDraggingStartPosition: Int? = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't hold the start position.
| @Test | ||
| fun `isUserDragging returns true when within grace period`() { | ||
| upNextQueue.setDragStartTimeForTesting(1_000L) | ||
| // checking 4 seconds later (within 10 second grace period) |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment incorrectly states "within 10 second grace period" when the actual DRAG_GRACE_PERIOD is 1 minute (60 seconds). Update the comment to say "within 1 minute grace period" or "within 60 second grace period" for accuracy.
| // checking 4 seconds later (within 10 second grace period) | |
| // checking 4 seconds later (within 1 minute grace period) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops I have changed this.
Description
There's an issue when rearranging the Up Next list and the Up Next sync that happens every five seconds. At the moment, while you are dragging, the sync can happen in the background, causing the UI to update incorrectly.
Fixes https://linear.app/a8c/issue/PCDROID-375/dragging-up-next-and-syncing-at-the-same-time
Testing Instructions
UpNextSyncto see when the Up Next sync happensScreencast
After
Up.Next.Drag.After.mov
Before
Up.Next.Drag.Before.mov
Checklist
./gradlew spotlessApplyto automatically apply formatting/linting)modules/services/localization/src/main/res/values/strings.xmlI have tested any UI changes...