From 110d29c50a8b892507f92eb0608340938f7cab2a Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 19 Jul 2023 09:26:24 +0200 Subject: [PATCH] feat: add 'Continue watching' section to home tab --- .../com/github/libretube/db/DatabaseHelper.kt | 14 ++++++++ .../libretube/db/obj/WatchHistoryItem.kt | 16 ++++++++- .../github/libretube/extensions/LocalDate.kt | 7 ++++ .../libretube/ui/fragments/HomeFragment.kt | 32 +++++++++++++++++ .../ui/fragments/SubscriptionsFragment.kt | 34 ++++++------------- app/src/main/res/layout/fragment_home.xml | 13 +++++++ app/src/main/res/values/array.xml | 2 ++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 95 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/github/libretube/extensions/LocalDate.kt diff --git a/app/src/main/java/com/github/libretube/db/DatabaseHelper.kt b/app/src/main/java/com/github/libretube/db/DatabaseHelper.kt index a613b4f86..10e525cdb 100644 --- a/app/src/main/java/com/github/libretube/db/DatabaseHelper.kt +++ b/app/src/main/java/com/github/libretube/db/DatabaseHelper.kt @@ -1,5 +1,6 @@ package com.github.libretube.db +import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.Streams import com.github.libretube.constants.PreferenceKeys import com.github.libretube.db.DatabaseHolder.Database @@ -50,4 +51,17 @@ object DatabaseHelper { searchHistory.removeFirst() } } + + suspend fun filterUnwatched(streams: List): List { + return streams.filter { + withContext(Dispatchers.IO) { + val historyItem = Database.watchPositionDao() + .findById(it.url.orEmpty().toID()) ?: return@withContext true + val progress = historyItem.position / 1000 + val duration = it.duration ?: 0 + // show video only in feed when watched less than 90% + progress < 0.9f * duration + } + } + } } diff --git a/app/src/main/java/com/github/libretube/db/obj/WatchHistoryItem.kt b/app/src/main/java/com/github/libretube/db/obj/WatchHistoryItem.kt index 54503d0b6..26f27097c 100644 --- a/app/src/main/java/com/github/libretube/db/obj/WatchHistoryItem.kt +++ b/app/src/main/java/com/github/libretube/db/obj/WatchHistoryItem.kt @@ -3,6 +3,8 @@ package com.github.libretube.db.obj import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import com.github.libretube.api.obj.StreamItem +import com.github.libretube.extensions.toMillis import kotlinx.datetime.LocalDate import kotlinx.serialization.Serializable @@ -17,4 +19,16 @@ data class WatchHistoryItem( @ColumnInfo var uploaderAvatar: String? = null, @ColumnInfo var thumbnailUrl: String? = null, @ColumnInfo val duration: Long? = null -) +) { + fun toStreamItem() = StreamItem( + url = videoId, + type = "stream", + title = title, + thumbnail = thumbnailUrl, + uploaderName = uploader, + uploaded = uploadDate?.toMillis(), + uploaderAvatar = uploaderAvatar, + uploaderUrl = uploaderUrl, + duration = duration + ) +} diff --git a/app/src/main/java/com/github/libretube/extensions/LocalDate.kt b/app/src/main/java/com/github/libretube/extensions/LocalDate.kt new file mode 100644 index 000000000..d8eab8afc --- /dev/null +++ b/app/src/main/java/com/github/libretube/extensions/LocalDate.kt @@ -0,0 +1,7 @@ +package com.github.libretube.extensions + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn + +fun LocalDate.toMillis() = this.atStartOfDayIn(TimeZone.UTC).toEpochMilliseconds() \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt index 2952c67aa..ff1adc4a7 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt @@ -21,8 +21,10 @@ import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.SubscriptionHelper import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.FragmentHomeBinding +import com.github.libretube.db.DatabaseHelper import com.github.libretube.db.DatabaseHolder import com.github.libretube.helpers.LocaleHelper +import com.github.libretube.helpers.PlayerHelper import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.ui.adapters.PlaylistBookmarkAdapter import com.github.libretube.ui.adapters.PlaylistsAdapter @@ -56,6 +58,10 @@ class HomeFragment : Fragment() { findNavController().navigate(R.id.subscriptionsFragment) } + binding.watchingTV.setOnClickListener { + findNavController().navigate(R.id.watchHistoryFragment) + } + binding.trendingTV.setOnClickListener { findNavController().navigate(R.id.trendsFragment) } @@ -90,6 +96,7 @@ class HomeFragment : Fragment() { .getStringSet(PreferenceKeys.HOME_TAB_CONTENT, defaultItems.toSet()) awaitAll( async { if (visibleItems.contains(TRENDING)) loadTrending() }, + async { if (visibleItems.contains(WATCHING)) loadVideosToContinueWatching() }, async { if (visibleItems.contains(BOOKMARKS)) loadBookmarks() }, async { if (visibleItems.contains(FEATURED)) loadFeed() }, async { if (visibleItems.contains(PLAYLISTS)) loadPlaylists() } @@ -200,6 +207,30 @@ class HomeFragment : Fragment() { }) } + private suspend fun loadVideosToContinueWatching() { + if (!PlayerHelper.watchHistoryEnabled) return + + val videos = withContext(Dispatchers.IO) { + DatabaseHolder.Database.watchHistoryDao().getAll() + } + val unwatchedVideos = DatabaseHelper.filterUnwatched(videos.map { it.toStreamItem() }) + .reversed() + .take(20) + if (unwatchedVideos.isEmpty()) return + val binding = _binding ?: return + + makeVisible(binding.watchingRV, binding.watchingTV) + binding.watchingRV.layoutManager = LinearLayoutManager( + context, + LinearLayoutManager.HORIZONTAL, + false + ) + binding.watchingRV.adapter = VideosAdapter( + unwatchedVideos.toMutableList(), + forceMode = VideosAdapter.Companion.ForceMode.HOME + ) + } + private fun makeVisible(vararg views: View) { views.forEach { it.isVisible = true @@ -213,6 +244,7 @@ class HomeFragment : Fragment() { companion object { // The values of the preference entries for the home tab content private const val FEATURED = "featured" + private const val WATCHING = "watching" private const val TRENDING = "trending" private const val BOOKMARKS = "bookmarks" private const val PLAYLISTS = "playlists" diff --git a/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt index ebc9fb81a..34fca9692 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt @@ -20,6 +20,7 @@ import com.github.libretube.R import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.FragmentSubscriptionsBinding +import com.github.libretube.db.DatabaseHelper import com.github.libretube.db.DatabaseHolder import com.github.libretube.db.obj.SubscriptionGroup import com.github.libretube.extensions.dpToPx @@ -33,7 +34,6 @@ import com.github.libretube.ui.models.SubscriptionsViewModel import com.github.libretube.ui.sheets.BaseBottomSheet import com.github.libretube.ui.sheets.ChannelGroupsSheet import com.google.android.material.chip.Chip -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -243,15 +243,16 @@ class SubscriptionsFragment : Fragment() { else -> throw IllegalArgumentException() } }.let { streams -> - runBlocking { - if (!PreferenceHelper.getBoolean( - PreferenceKeys.HIDE_WATCHED_FROM_FEED, - false - ) - ) { - streams - } else { - removeWatchVideosFromFeed(streams) + + if (!PreferenceHelper.getBoolean( + PreferenceKeys.HIDE_WATCHED_FROM_FEED, + false + ) + ) { + streams + } else { + runBlocking { + DatabaseHelper.filterUnwatched(streams) } } } @@ -294,19 +295,6 @@ class SubscriptionsFragment : Fragment() { PreferenceHelper.updateLastFeedWatchedTime() } - private fun removeWatchVideosFromFeed(streams: List): List { - return streams.filter { - runBlocking(Dispatchers.IO) { - val historyItem = DatabaseHolder.Database.watchPositionDao() - .findById(it.url.orEmpty().toID()) ?: return@runBlocking true - val progress = historyItem.position / 1000 - val duration = it.duration ?: 0 - // show video only in feed when watched less than 1/4 - progress < 0.9f * duration - } - } - } - private fun showSubscriptions() { if (viewModel.subscriptions.value == null) return diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 5f64b5dc8..be3d61dcd 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -39,6 +39,19 @@ android:nestedScrollingEnabled="false" android:visibility="gone" /> + + + + @string/featured + @string/continue_watching @string/trending @string/bookmarks @string/playlists @@ -434,6 +435,7 @@ featured + watching trending bookmarks playlists diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 76d900791..93406fc97 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -452,6 +452,7 @@ descriptive default or unknown unknown or no audio + Continue watching Download Service