mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 22:30:30 +05:30
feat: add 'Continue watching' section to home tab
This commit is contained in:
parent
e75bbc868e
commit
110d29c50a
@ -1,5 +1,6 @@
|
|||||||
package com.github.libretube.db
|
package com.github.libretube.db
|
||||||
|
|
||||||
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.github.libretube.db.DatabaseHolder.Database
|
import com.github.libretube.db.DatabaseHolder.Database
|
||||||
@ -50,4 +51,17 @@ object DatabaseHelper {
|
|||||||
searchHistory.removeFirst()
|
searchHistory.removeFirst()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun filterUnwatched(streams: List<StreamItem>): List<StreamItem> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package com.github.libretube.db.obj
|
|||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.libretube.api.obj.StreamItem
|
||||||
|
import com.github.libretube.extensions.toMillis
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@ -17,4 +19,16 @@ data class WatchHistoryItem(
|
|||||||
@ColumnInfo var uploaderAvatar: String? = null,
|
@ColumnInfo var uploaderAvatar: String? = null,
|
||||||
@ColumnInfo var thumbnailUrl: String? = null,
|
@ColumnInfo var thumbnailUrl: String? = null,
|
||||||
@ColumnInfo val duration: Long? = 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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()
|
@ -21,8 +21,10 @@ import com.github.libretube.api.RetrofitInstance
|
|||||||
import com.github.libretube.api.SubscriptionHelper
|
import com.github.libretube.api.SubscriptionHelper
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.github.libretube.databinding.FragmentHomeBinding
|
import com.github.libretube.databinding.FragmentHomeBinding
|
||||||
|
import com.github.libretube.db.DatabaseHelper
|
||||||
import com.github.libretube.db.DatabaseHolder
|
import com.github.libretube.db.DatabaseHolder
|
||||||
import com.github.libretube.helpers.LocaleHelper
|
import com.github.libretube.helpers.LocaleHelper
|
||||||
|
import com.github.libretube.helpers.PlayerHelper
|
||||||
import com.github.libretube.helpers.PreferenceHelper
|
import com.github.libretube.helpers.PreferenceHelper
|
||||||
import com.github.libretube.ui.adapters.PlaylistBookmarkAdapter
|
import com.github.libretube.ui.adapters.PlaylistBookmarkAdapter
|
||||||
import com.github.libretube.ui.adapters.PlaylistsAdapter
|
import com.github.libretube.ui.adapters.PlaylistsAdapter
|
||||||
@ -56,6 +58,10 @@ class HomeFragment : Fragment() {
|
|||||||
findNavController().navigate(R.id.subscriptionsFragment)
|
findNavController().navigate(R.id.subscriptionsFragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.watchingTV.setOnClickListener {
|
||||||
|
findNavController().navigate(R.id.watchHistoryFragment)
|
||||||
|
}
|
||||||
|
|
||||||
binding.trendingTV.setOnClickListener {
|
binding.trendingTV.setOnClickListener {
|
||||||
findNavController().navigate(R.id.trendsFragment)
|
findNavController().navigate(R.id.trendsFragment)
|
||||||
}
|
}
|
||||||
@ -90,6 +96,7 @@ class HomeFragment : Fragment() {
|
|||||||
.getStringSet(PreferenceKeys.HOME_TAB_CONTENT, defaultItems.toSet())
|
.getStringSet(PreferenceKeys.HOME_TAB_CONTENT, defaultItems.toSet())
|
||||||
awaitAll(
|
awaitAll(
|
||||||
async { if (visibleItems.contains(TRENDING)) loadTrending() },
|
async { if (visibleItems.contains(TRENDING)) loadTrending() },
|
||||||
|
async { if (visibleItems.contains(WATCHING)) loadVideosToContinueWatching() },
|
||||||
async { if (visibleItems.contains(BOOKMARKS)) loadBookmarks() },
|
async { if (visibleItems.contains(BOOKMARKS)) loadBookmarks() },
|
||||||
async { if (visibleItems.contains(FEATURED)) loadFeed() },
|
async { if (visibleItems.contains(FEATURED)) loadFeed() },
|
||||||
async { if (visibleItems.contains(PLAYLISTS)) loadPlaylists() }
|
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) {
|
private fun makeVisible(vararg views: View) {
|
||||||
views.forEach {
|
views.forEach {
|
||||||
it.isVisible = true
|
it.isVisible = true
|
||||||
@ -213,6 +244,7 @@ class HomeFragment : Fragment() {
|
|||||||
companion object {
|
companion object {
|
||||||
// The values of the preference entries for the home tab content
|
// The values of the preference entries for the home tab content
|
||||||
private const val FEATURED = "featured"
|
private const val FEATURED = "featured"
|
||||||
|
private const val WATCHING = "watching"
|
||||||
private const val TRENDING = "trending"
|
private const val TRENDING = "trending"
|
||||||
private const val BOOKMARKS = "bookmarks"
|
private const val BOOKMARKS = "bookmarks"
|
||||||
private const val PLAYLISTS = "playlists"
|
private const val PLAYLISTS = "playlists"
|
||||||
|
@ -20,6 +20,7 @@ import com.github.libretube.R
|
|||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.github.libretube.databinding.FragmentSubscriptionsBinding
|
import com.github.libretube.databinding.FragmentSubscriptionsBinding
|
||||||
|
import com.github.libretube.db.DatabaseHelper
|
||||||
import com.github.libretube.db.DatabaseHolder
|
import com.github.libretube.db.DatabaseHolder
|
||||||
import com.github.libretube.db.obj.SubscriptionGroup
|
import com.github.libretube.db.obj.SubscriptionGroup
|
||||||
import com.github.libretube.extensions.dpToPx
|
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.BaseBottomSheet
|
||||||
import com.github.libretube.ui.sheets.ChannelGroupsSheet
|
import com.github.libretube.ui.sheets.ChannelGroupsSheet
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
@ -243,7 +243,7 @@ class SubscriptionsFragment : Fragment() {
|
|||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}.let { streams ->
|
}.let { streams ->
|
||||||
runBlocking {
|
|
||||||
if (!PreferenceHelper.getBoolean(
|
if (!PreferenceHelper.getBoolean(
|
||||||
PreferenceKeys.HIDE_WATCHED_FROM_FEED,
|
PreferenceKeys.HIDE_WATCHED_FROM_FEED,
|
||||||
false
|
false
|
||||||
@ -251,7 +251,8 @@ class SubscriptionsFragment : Fragment() {
|
|||||||
) {
|
) {
|
||||||
streams
|
streams
|
||||||
} else {
|
} else {
|
||||||
removeWatchVideosFromFeed(streams)
|
runBlocking {
|
||||||
|
DatabaseHelper.filterUnwatched(streams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,19 +295,6 @@ class SubscriptionsFragment : Fragment() {
|
|||||||
PreferenceHelper.updateLastFeedWatchedTime()
|
PreferenceHelper.updateLastFeedWatchedTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeWatchVideosFromFeed(streams: List<StreamItem>): List<StreamItem> {
|
|
||||||
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() {
|
private fun showSubscriptions() {
|
||||||
if (viewModel.subscriptions.value == null) return
|
if (viewModel.subscriptions.value == null) return
|
||||||
|
|
||||||
|
@ -39,6 +39,19 @@
|
|||||||
android:nestedScrollingEnabled="false"
|
android:nestedScrollingEnabled="false"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/watchingTV"
|
||||||
|
style="@style/HomeCategoryTitle"
|
||||||
|
android:text="@string/continue_watching" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/watchingRV"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/trendingTV"
|
android:id="@+id/trendingTV"
|
||||||
style="@style/HomeCategoryTitle"
|
style="@style/HomeCategoryTitle"
|
||||||
|
@ -427,6 +427,7 @@
|
|||||||
|
|
||||||
<string-array name="homeTabItems">
|
<string-array name="homeTabItems">
|
||||||
<item>@string/featured</item>
|
<item>@string/featured</item>
|
||||||
|
<item>@string/continue_watching</item>
|
||||||
<item>@string/trending</item>
|
<item>@string/trending</item>
|
||||||
<item>@string/bookmarks</item>
|
<item>@string/bookmarks</item>
|
||||||
<item>@string/playlists</item>
|
<item>@string/playlists</item>
|
||||||
@ -434,6 +435,7 @@
|
|||||||
|
|
||||||
<string-array name="homeTabItemsValues">
|
<string-array name="homeTabItemsValues">
|
||||||
<item>featured</item>
|
<item>featured</item>
|
||||||
|
<item>watching</item>
|
||||||
<item>trending</item>
|
<item>trending</item>
|
||||||
<item>bookmarks</item>
|
<item>bookmarks</item>
|
||||||
<item>playlists</item>
|
<item>playlists</item>
|
||||||
|
@ -452,6 +452,7 @@
|
|||||||
<string name="descriptive_audio_track">descriptive</string>
|
<string name="descriptive_audio_track">descriptive</string>
|
||||||
<string name="default_or_unknown_audio_track">default or unknown</string>
|
<string name="default_or_unknown_audio_track">default or unknown</string>
|
||||||
<string name="unknown_or_no_audio">unknown or no audio</string>
|
<string name="unknown_or_no_audio">unknown or no audio</string>
|
||||||
|
<string name="continue_watching">Continue watching</string>
|
||||||
|
|
||||||
<!-- Notification channel strings -->
|
<!-- Notification channel strings -->
|
||||||
<string name="download_channel_name">Download Service</string>
|
<string name="download_channel_name">Download Service</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user