Skip to content

Migrate watch history to Jetpack Compose #11947

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

Open
wants to merge 39 commits into
base: refactor
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
169eff8
Rewrite history fragment using Compose
Isira-Seneviratne Jan 14, 2025
77e254b
Remove unused classes
Isira-Seneviratne Jan 14, 2025
3d9394e
Reuse stream composables
Isira-Seneviratne Jan 14, 2025
f99fc13
Reuse ItemList composable
Isira-Seneviratne Jan 15, 2025
4ec5492
Update dependencies
Isira-Seneviratne Jan 15, 2025
38a533f
Combine ItemList code
Isira-Seneviratne Jan 15, 2025
3f1bd58
Rm unused file
Isira-Seneviratne Jan 15, 2025
48ad123
Implement clear history functionality
Isira-Seneviratne Jan 15, 2025
38645b0
Implement playback action buttons
Isira-Seneviratne Jan 20, 2025
fa98a92
Cache history in view model
Isira-Seneviratne Jan 20, 2025
6621b7f
Fix NPE
Isira-Seneviratne Jan 20, 2025
aeb4548
Add delete button for history items
Isira-Seneviratne Jan 21, 2025
3943a87
Fix preview issue
Isira-Seneviratne Jan 21, 2025
bf06923
Reuse dropdown composable
Isira-Seneviratne Jan 21, 2025
59a748d
Merge branch 'refactor' into History-Compose
Isira-Seneviratne Jan 24, 2025
21caa6c
Update Compose BOM
Isira-Seneviratne Jan 25, 2025
5abc03c
Extract thumbnail into common composable
Isira-Seneviratne Jan 27, 2025
6de5ce7
Merge branch 'refactor' into History-Compose
Isira-Seneviratne Jan 31, 2025
61bb81f
Fix black theme, remove manual background definition
Isira-Seneviratne Jan 31, 2025
212c678
Remove manual text color specification
Isira-Seneviratne Jan 31, 2025
ac34ada
Make DropdownTextMenuItem non-restartable
Isira-Seneviratne Feb 1, 2025
695c05b
Add confirmation dialog for clearing watch history
Isira-Seneviratne Feb 5, 2025
dbe3011
Remove Unknown class
Isira-Seneviratne Feb 9, 2025
491b433
Merge branch 'refactor' into History-Compose
Isira-Seneviratne Feb 22, 2025
e877997
Improve constructors
Isira-Seneviratne Mar 6, 2025
975ba3c
Reduce button size, fix alignment
Isira-Seneviratne Mar 8, 2025
851ba4b
Use segmented button for sort options
Isira-Seneviratne Mar 11, 2025
033d288
Use "Sort By" label, remove outdated translations
Isira-Seneviratne Mar 16, 2025
2bafa2c
Add loading indicator in ItemList
Isira-Seneviratne Mar 24, 2025
54c2b49
Restore clear menu
Isira-Seneviratne Mar 25, 2025
a58b0da
Fix actions
Isira-Seneviratne Mar 25, 2025
e00e256
Rename .java to .kt
Isira-Seneviratne Mar 26, 2025
fe563f3
Improve DAO method calls
Isira-Seneviratne Mar 26, 2025
6f9af26
Update dependencies
Isira-Seneviratne Mar 26, 2025
287db90
Sort using last played by default
Isira-Seneviratne Mar 31, 2025
451c89b
Merge branch 'refactor' into History-Compose
Isira-Seneviratne Apr 10, 2025
27a29ae
Update Jetpack Compose and Room
Isira-Seneviratne Apr 17, 2025
eb6a03a
Merge branch 'refactor' into History-Compose
Isira-Seneviratne May 14, 2025
12a97c6
Merge branch 'refactor' into History-Compose
Isira-Seneviratne Jun 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ dependencies {
implementation libs.androidx.recyclerview
implementation libs.androidx.room.runtime
implementation libs.androidx.room.rxjava3
implementation libs.androidx.room.paging
kapt libs.androidx.room.compiler
implementation libs.androidx.swiperefreshlayout
implementation libs.androidx.work.runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class DatabaseMigrationTest {
)

val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
val listFromDB = migratedDatabaseV3.streamDAO().getAll().blockingFirst()

// Only expect 2, the one with the null url will be ignored
assertEquals(2, listFromDB.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package org.schabi.newpipe.database
import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.rx3.await
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
Expand All @@ -22,7 +24,6 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo
import org.schabi.newpipe.extractor.stream.StreamType
import java.io.IOException
import java.time.OffsetDateTime
import kotlin.streams.toList

class FeedDAOTest {
private lateinit var db: AppDatabase
Expand Down Expand Up @@ -94,14 +95,10 @@ class FeedDAOTest {
)
}

private fun setupUnlinkDelete(time: String) {
private fun setupUnlinkDelete(time: String) = runBlocking(Dispatchers.IO) {
clearAndFillTables()
Single.fromCallable {
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
}.blockingSubscribe()
Single.fromCallable {
streamDAO.deleteOrphans()
}.blockingSubscribe()
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
streamDAO.deleteOrphans().await()
}

private fun clearAndFillTables() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ class LocalPlaylistManagerTest {
val result = manager.createPlaylist("name", listOf(stream, upserted))

result.test().await().assertComplete()
database.streamDAO().all.test().awaitCount(1).assertValue(listOf(stream, upserted))
database.streamDAO().getAll().test().awaitCount(1).assertValue(listOf(stream, upserted))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ abstract class FeedDAO {
AND s.upload_date <> max_upload_date))
"""
)
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
abstract suspend fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)

@Query(
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
package org.schabi.newpipe.database.history.dao;

import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.RewriteQueriesToDropUnusedColumns;

import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;

import java.util.List;

import io.reactivex.rxjava3.core.Flowable;

import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
Expand All @@ -25,65 +12,95 @@
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;

@Dao
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE
+ " WHERE " + STREAM_ACCESS_DATE + " = "
+ "(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")")
@Override
@Nullable
public abstract StreamHistoryEntity getLatestEntry();
import androidx.annotation.Nullable;
import androidx.paging.PagingSource;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.RewriteQueriesToDropUnusedColumns;

@Override
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE)
public abstract Flowable<List<StreamHistoryEntity>> getAll();
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;

@Override
@Query("DELETE FROM " + STREAM_HISTORY_TABLE)
public abstract int deleteAll();
import java.util.List;

@Override
public Flowable<List<StreamHistoryEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException();
}
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;

@Query("SELECT * FROM " + STREAM_TABLE
+ " INNER JOIN " + STREAM_HISTORY_TABLE
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " ORDER BY " + STREAM_ACCESS_DATE + " DESC")
public abstract Flowable<List<StreamHistoryEntry>> getHistory();
@Dao
public interface StreamHistoryDAO {
@Insert
long insert(StreamHistoryEntity entity);

@Delete
void delete(StreamHistoryEntity entity);

@Query("DELETE FROM " + STREAM_HISTORY_TABLE)
Completable deleteAll();

@Query("SELECT * FROM " + STREAM_TABLE
+ " INNER JOIN " + STREAM_HISTORY_TABLE
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " ORDER BY " + STREAM_ID + " ASC")
public abstract Flowable<List<StreamHistoryEntry>> getHistorySortedById();
Flowable<List<StreamHistoryEntry>> getHistorySortedById();

@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID
+ " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
@Nullable
public abstract StreamHistoryEntity getLatestEntry(long streamId);
StreamHistoryEntity getLatestEntry(long streamId);

@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
public abstract int deleteStreamHistory(long streamId);
Completable deleteStreamHistory(long streamId);

@Query("SELECT * FROM " + STREAM_TABLE
+ " INNER JOIN " + STREAM_HISTORY_TABLE
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " ORDER BY " + STREAM_ACCESS_DATE + " DESC")
Flowable<List<StreamHistoryEntry>> getHistory();

@RewriteQueriesToDropUnusedColumns
@Query("SELECT * FROM " + STREAM_TABLE
// Select the latest entry and watch count for each stream id on history table
+ " INNER JOIN "
+ "(SELECT " + JOIN_STREAM_ID + ", "
+ " MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", "
+ " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
+ " FROM " + STREAM_HISTORY_TABLE
+ " GROUP BY " + JOIN_STREAM_ID + ")"

+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID

+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_MILLIS
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS

+ " ORDER BY " + STREAM_LATEST_DATE + " DESC"
)
PagingSource<Integer, StreamStatisticsEntry> getHistoryOrderedByLastWatched();

@RewriteQueriesToDropUnusedColumns
@Query("SELECT * FROM " + STREAM_TABLE
// Select the latest entry and watch count for each stream id on history table
+ " INNER JOIN "
+ "(SELECT " + JOIN_STREAM_ID + ", "
+ " MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", "
+ " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
+ " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")"
+ " FROM " + STREAM_HISTORY_TABLE
+ " GROUP BY " + JOIN_STREAM_ID + ")"

+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID

+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_MILLIS
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS)
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS

+ " ORDER BY " + STREAM_WATCH_COUNT + " DESC"
)
PagingSource<Integer, StreamStatisticsEntry> getHistoryOrderedByViewCount();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
import java.time.OffsetDateTime

class StreamStatisticsEntry(
Expand All @@ -26,16 +24,6 @@ class StreamStatisticsEntry(
@ColumnInfo(name = STREAM_WATCH_COUNT)
val watchCount: Long
) : LocalItem {
fun toStreamInfoItem(): StreamInfoItem {
val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType)
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)

return item
}

override fun getLocalItemType(): LocalItem.LocalItemType {
return LocalItem.LocalItemType.STATISTIC_STREAM_ITEM
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
@Query("SELECT * FROM streams WHERE url = :url AND service_id = :serviceId")
abstract fun getStream(serviceId: Long, url: String): Maybe<StreamEntity>

@Query("UPDATE streams SET uploader_url = :uploaderUrl WHERE url = :url AND service_id = :serviceId")
abstract fun setUploaderUrl(serviceId: Long, url: String, uploaderUrl: String): Completable

@Insert(onConflict = OnConflictStrategy.IGNORE)
internal abstract fun silentInsertInternal(stream: StreamEntity): Long

Expand Down Expand Up @@ -123,7 +120,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
WHERE f.stream_id = streams.uid)
"""
)
abstract fun deleteOrphans(): Int
abstract fun deleteOrphans(): Completable

/**
* Minimal entry class used when comparing/updating an existent stream.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.schabi.newpipe.database.stream.dao

import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Maybe
import org.schabi.newpipe.database.stream.model.StreamStateEntity

@Dao
interface StreamStateDAO {
@Query("DELETE FROM " + StreamStateEntity.STREAM_STATE_TABLE)
fun deleteAll(): Completable

@Query("SELECT * FROM " + StreamStateEntity.STREAM_STATE_TABLE + " WHERE " + StreamStateEntity.JOIN_STREAM_ID + " = :streamId")
fun getState(streamId: Long): Maybe<StreamStateEntity>

@Query("DELETE FROM " + StreamStateEntity.STREAM_STATE_TABLE + " WHERE " + StreamStateEntity.JOIN_STREAM_ID + " = :streamId")
fun deleteState(streamId: Long): Completable

@Upsert
fun upsert(stream: StreamStateEntity)
}
Loading