Migrate watch history to Jetpack Compose
What is it?
- [ ] Bugfix (user facing)
- [x] Feature (user facing)
- [x] Codebase improvement (dev facing)
- [ ] Meta improvement to the project (dev facing)
Description of the changes in your PR
- Rewrite the watch history UI using Jetpack Compose. As part of this change, the ItemList composable was refactored to use separate data classes, in order to further decouple it from the information provided by the extractor and make it more easily reusable.
- Clean up unused methods in StreamHistoryDAO.
- Reimplement the watch history retrieval from the Room database using AndroidX Paging for improved efficiency, as it supports pagination.
- Add stream card and grid composables.
- Update several relevant dependencies, including the Compose BOM version.
- Use loading indicator for placeholder items in ItemList.
Before/After Screenshots/Screen Record
| Before | After |
|---|---|
https://github.com/user-attachments/assets/278e4fa0-a89b-459a-a3bb-2931c4f36dfb
Fixes the following issue(s)
- Fixes #
APK testing
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. You can find more info and a video demonstration on this wiki page.
Due diligence
- [x] I read the contribution guidelines.
There should be a confirmation screen after "clear history" button clicked I guess
Is it possible to put get the background, pop up, and play all actions in a single row? The buttons and sort by drop down use a lot of vertical space.
What is the reasoning to make the sort by box not to grow to the full width of its parent container in portrait mode?
Is it possible to put get the background, pop up, and play all actions in a single row? The buttons and sort by drop down use a lot of vertical space. What is the reasoning to make the
sort bybox not to grow to the full width of its parent container in portrait mode?
A scrollable row could be used to keep the buttons in the same row (at the moment, a FlowRow is used to adjust the buttons if the screen width is too narrow). The sort box could also be included in the same row if needed.
@TobiGr I revised the UI again to use a segmented button instead of a dropdown.
thanks. I am wondering whether the checkmark is the right icon to use here. Did you try to use the sort icon?
No, but I added the Sort By label for clarity.
I would put the Clear button in the three-dot menu, it's rarely used and especially should not be pressed on by accident.
I would put the Clear button in the three-dot menu, it's rarely used and especially should not be pressed on by accident.
It opens a confirmation dialog first.
I know, but I still think it would be better in the 3-dot menu (also because it would use up less space). Consider that if somebody doesn't want history they would just disable it, they wouldn't keep using the clear button.
Good point. I made the change.
No, but I added the Sort By label for clarity.
Would you please restore earlier default behavior of "sort by" to "date", it defaults to "views" in this PR.
Also noticed a minor bug: last selected option isn't saved, it switches to sort by "views" even after manually changing it to sort by "date" after reopening the app.
No, but I added the Sort By label for clarity.
Would you please restore earlier default behavior of "sort by" to "date", it defaults to "views" in this PR.
Okay.
Also noticed a minor bug: last selected option isn't saved, it switches to sort by "views" even after manually changing it to sort by "date" after reopening the app.
That's because the selected option is not being persisted (the existing app doesn't do it, either).
Quality Gate passed
Issues
9 New issues
0 Accepted issues
Measures
0 Security Hotspots
0.0% Coverage on New Code
0.0% Duplication on New Code
Quality Gate passed
Issues
9 New issues
0 Accepted issues
Measures
0 Security Hotspots
0.0% Coverage on New Code
0.0% Duplication on New Code
Produces Error on data export, I can export with playlist fragment to Jetpack compose PR fine (as I am currently testing the two) It looks specific to this PR or Is it related to what is for #12415 in the first place?
Exception
- User Action: database import or export
- Request: Exporting database and settings
- Content Country: GB
- Content Language: en-GB
- App Language: en_GB
- Service: none
- Timestamp: 2025-07-11T22:40:21.929+05:30
- Package: org.schabi.newpipe.debug.HistoryCompose
- Service: none
- Version: 0.27.7
- OS: Linux samsung/a22xnnxx/a22x:13/TP1A.220624.014/A226BXXU7DWH4:user/release-keys 13 - 33
Crash log
[java.lang.IllegalStateException](https://github.com/TeamNewPipe/NewPipe/pull/java.lang.IllegalStateException): Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at [androidx.room.RoomDatabase.assertNotMainThread](https://github.com/TeamNewPipe/NewPipe/pull/androidx.room.RoomDatabase.assertNotMainThread)(roomdatabase.android.kt:562)
at [androidx.room.RoomDatabase.query](https://github.com/TeamNewPipe/NewPipe/pull/androidx.room.RoomDatabase.query)(roomdatabase.android.kt:612)
at [org.schabi.newpipe.NewPipeDatabase.checkpoint](https://github.com/TeamNewPipe/NewPipe/pull/org.schabi.newpipe.NewPipeDatabase.checkpoint)(newpipedatabase.java:56)
at [org.schabi.newpipe.settings.BackupRestoreSettingsFragment.exportDatabase](https://github.com/TeamNewPipe/NewPipe/pull/org.schabi.newpipe.settings.BackupRestoreSettingsFragment.exportDatabase)(backuprestoresettingsfragment.java:163)
at [org.schabi.newpipe.settings.BackupRestoreSettingsFragment.requestExportPathResult](https://github.com/TeamNewPipe/NewPipe/pull/org.schabi.newpipe.settings.BackupRestoreSettingsFragment.requestExportPathResult)(backuprestoresettingsfragment.java:137)
at [org.schabi.newpipe.settings.BackupRestoreSettingsFragment](https://github.com/TeamNewPipe/NewPipe/pull/org.schabi.newpipe.settings.BackupRestoreSettingsFragment).$r8$lambda$fGteoRaC9h1Pn2xEuX2RRkPtyno(Unknown Source:0)
at [org.schabi.newpipe.settings.BackupRestoreSettingsFragment](https://github.com/TeamNewPipe/NewPipe/pull/org.schabi.newpipe.settings.BackupRestoreSettingsFragment)$$[ExternalSyntheticLambda8.onActivityResult](https://github.com/TeamNewPipe/NewPipe/pull/ExternalSyntheticLambda8.onActivityResult)(D8$$SyntheticClass:0)
at [androidx.activity.result.ActivityResultRegistry.doDispatch](https://github.com/TeamNewPipe/NewPipe/pull/androidx.activity.result.ActivityResultRegistry.doDispatch)(activityresultregistry.java:414)
at [androidx.activity.result.ActivityResultRegistry.dispatchResult](https://github.com/TeamNewPipe/NewPipe/pull/androidx.activity.result.ActivityResultRegistry.dispatchResult)(activityresultregistry.java:371)
at [androidx.activity.ComponentActivity.onActivityResult](https://github.com/TeamNewPipe/NewPipe/pull/androidx.activity.ComponentActivity.onActivityResult)(componentactivity.java:845)
at [androidx.fragment.app.FragmentActivity.onActivityResult](https://github.com/TeamNewPipe/NewPipe/pull/androidx.fragment.app.FragmentActivity.onActivityResult)(fragmentactivity.java:151)
at [android.app.Activity.dispatchActivityResult](https://github.com/TeamNewPipe/NewPipe/pull/android.app.Activity.dispatchActivityResult)(activity.java:8951)
at [android.app.ActivityThread.deliverResults](https://github.com/TeamNewPipe/NewPipe/pull/android.app.ActivityThread.deliverResults)(activitythread.java:5987)
at [android.app.ActivityThread.handleSendResult](https://github.com/TeamNewPipe/NewPipe/pull/android.app.ActivityThread.handleSendResult)(activitythread.java:6033)
at [android.app.servertransaction.ActivityResultItem.execute](https://github.com/TeamNewPipe/NewPipe/pull/android.app.servertransaction.ActivityResultItem.execute)(activityresultitem.java:67)
at [android.app.servertransaction.ActivityTransactionItem.execute](https://github.com/TeamNewPipe/NewPipe/pull/android.app.servertransaction.ActivityTransactionItem.execute)(activitytransactionitem.java:45)
at [android.app.servertransaction.TransactionExecutor.executeCallbacks](https://github.com/TeamNewPipe/NewPipe/pull/android.app.servertransaction.TransactionExecutor.executeCallbacks)(transactionexecutor.java:135)
at [android.app.servertransaction.TransactionExecutor.execute](https://github.com/TeamNewPipe/NewPipe/pull/android.app.servertransaction.TransactionExecutor.execute)(transactionexecutor.java:95)
at [android.app.ActivityThread](https://github.com/TeamNewPipe/NewPipe/pull/android.app.ActivityThread)$[H.handleMessage](https://github.com/TeamNewPipe/NewPipe/pull/H.handleMessage)(activitythread.java:2574)
at [android.os.Handler.dispatchMessage](https://github.com/TeamNewPipe/NewPipe/pull/android.os.Handler.dispatchMessage)(handler.java:106)
at [android.os.Looper.loopOnce](https://github.com/TeamNewPipe/NewPipe/pull/android.os.Looper.loopOnce)(looper.java:226)
at [android.os.Looper.loop](https://github.com/TeamNewPipe/NewPipe/pull/android.os.Looper.loop)(looper.java:313)
at [android.app.ActivityThread.main](https://github.com/TeamNewPipe/NewPipe/pull/android.app.ActivityThread.main)(activitythread.java:8762)
at [java.lang.reflect.Method.invoke](https://github.com/TeamNewPipe/NewPipe/pull/java.lang.reflect.Method.invoke)(Native Method)
at [com.android.internal.os.RuntimeInit](https://github.com/TeamNewPipe/NewPipe/pull/com.android.internal.os.RuntimeInit)$[MethodAndArgsCaller.run](https://github.com/TeamNewPipe/NewPipe/pull/MethodAndArgsCaller.run)(runtimeinit.java:604)
at [com.android.internal.os.ZygoteInit.main](https://github.com/TeamNewPipe/NewPipe/pull/com.android.internal.os.ZygoteInit.main)(zygoteinit.java:1067)
Produces Error on data export, I can export with playlist fragment to Jetpack compose PR fine (as I am currently testing the two) It looks specific to this PR or Is it related to what is for #12415 in the first place?
Yes, I had to update the Room version in this PR in order to use PagingSource.
@ShareASmile It should be working now.
Just did a quick test and now it works flawlessly. Thank you for all your efforts :).
