mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
feat: Improve new user experience + adjust home load (#5491)
* Added necessary translations * Added support for redirecting directly to IntentSettings * Create HomeViewModel * Used HomeViewModel * Update app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt Co-authored-by: Bnyro <82752168+Bnyro@users.noreply.github.com> * Update app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt Co-authored-by: Bnyro <82752168+Bnyro@users.noreply.github.com> * Update app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt Co-authored-by: Bnyro <82752168+Bnyro@users.noreply.github.com> * Swap elvis operator for if statement for improved readability. * Move runSafely to separate file * Change when statement by if statement Co-authored-by: Bnyro <82752168+Bnyro@users.noreply.github.com> * Format if statement * * Remove LiveData properties; * Change buttons style for consistency; * Move updateIfChanged to a separate file; --------- Co-authored-by: Bnyro <82752168+Bnyro@users.noreply.github.com>
This commit is contained in:
parent
3cb99a712d
commit
e31943f5ab
@ -0,0 +1,7 @@
|
||||
package com.github.libretube.extensions
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
||||
fun <T> MutableLiveData<T>.updateIfChanged(newValue: T) {
|
||||
if (value != newValue) value = newValue
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.github.libretube.extensions
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
suspend fun <T> runSafely(
|
||||
onSuccess: (List<T>) -> Unit = { },
|
||||
ioBlock: suspend () -> List<T>,
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val result = runCatching { ioBlock.invoke() }
|
||||
.getOrNull()
|
||||
?.takeIf { it.isNotEmpty() } ?: return@withContext
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (result.isNotEmpty()) {
|
||||
onSuccess.invoke(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,13 @@ package com.github.libretube.ui.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.addCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.fragment.app.replace
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.databinding.ActivitySettingsBinding
|
||||
import com.github.libretube.ui.base.BaseActivity
|
||||
import com.github.libretube.ui.preferences.InstanceSettings
|
||||
import com.github.libretube.ui.preferences.MainSettings
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
@ -24,9 +26,7 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager.commit {
|
||||
replace<MainSettings>(R.id.settings)
|
||||
}
|
||||
redirectTo<MainSettings>()
|
||||
}
|
||||
|
||||
// new way of dealing with back presses instead of onBackPressed()
|
||||
@ -34,15 +34,32 @@ class SettingsActivity : BaseActivity() {
|
||||
if (supportFragmentManager.findFragmentById(R.id.settings) is MainSettings) {
|
||||
finishAndRemoveTask()
|
||||
} else {
|
||||
supportFragmentManager.commit {
|
||||
replace<MainSettings>(R.id.settings)
|
||||
}
|
||||
redirectTo<MainSettings>()
|
||||
changeTopBarText(getString(R.string.settings))
|
||||
}
|
||||
}
|
||||
|
||||
handleRedirect()
|
||||
}
|
||||
|
||||
private fun handleRedirect() {
|
||||
val redirectKey = intent.extras?.getString(REDIRECT_KEY)
|
||||
|
||||
if (redirectKey == REDIRECT_TO_INTENT_SETTINGS) redirectTo<InstanceSettings>()
|
||||
}
|
||||
|
||||
fun changeTopBarText(text: String) {
|
||||
if (this::binding.isInitialized) binding.toolbar.title = text
|
||||
}
|
||||
|
||||
private inline fun <reified T : Fragment> redirectTo() {
|
||||
supportFragmentManager.commit {
|
||||
replace<T>(R.id.settings)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REDIRECT_KEY = "redirect"
|
||||
const val REDIRECT_TO_INTENT_SETTINGS = "intent_settings"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.github.libretube.ui.fragments
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -8,42 +9,34 @@ import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.api.PlaylistsHelper
|
||||
import com.github.libretube.api.RetrofitInstance
|
||||
import com.github.libretube.api.SubscriptionHelper
|
||||
import com.github.libretube.constants.PreferenceKeys
|
||||
import com.github.libretube.api.obj.Playlists
|
||||
import com.github.libretube.api.obj.StreamItem
|
||||
import com.github.libretube.constants.PreferenceKeys.HOME_TAB_CONTENT
|
||||
import com.github.libretube.databinding.FragmentHomeBinding
|
||||
import com.github.libretube.db.DatabaseHelper
|
||||
import com.github.libretube.db.DatabaseHolder
|
||||
import com.github.libretube.enums.ContentFilter
|
||||
import com.github.libretube.helpers.LocaleHelper
|
||||
import com.github.libretube.helpers.PlayerHelper
|
||||
import com.github.libretube.db.obj.PlaylistBookmark
|
||||
import com.github.libretube.helpers.PreferenceHelper
|
||||
import com.github.libretube.ui.activities.SettingsActivity
|
||||
import com.github.libretube.ui.adapters.PlaylistBookmarkAdapter
|
||||
import com.github.libretube.ui.adapters.PlaylistsAdapter
|
||||
import com.github.libretube.ui.adapters.VideosAdapter
|
||||
import com.github.libretube.ui.adapters.VideosAdapter.Companion.LayoutMode
|
||||
import com.github.libretube.ui.models.HomeViewModel
|
||||
import com.github.libretube.ui.models.SubscriptionsViewModel
|
||||
import com.github.libretube.util.deArrow
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
class HomeFragment : Fragment() {
|
||||
private var _binding: FragmentHomeBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val subscriptionsViewModel: SubscriptionsViewModel by activityViewModels()
|
||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@ -82,7 +75,50 @@ class HomeFragment : Fragment() {
|
||||
fetchHomeFeed()
|
||||
}
|
||||
|
||||
fetchHomeFeed()
|
||||
binding.refreshButton.setOnClickListener {
|
||||
fetchHomeFeed()
|
||||
}
|
||||
|
||||
binding.changeInstance.setOnClickListener {
|
||||
redirectToIntentSettings()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
observeChanges()
|
||||
|
||||
// Avoid re-fetching when re-entering the screen if it was loaded successfully
|
||||
if (homeViewModel.loadedSuccessfully.value == false) {
|
||||
fetchHomeFeed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeChanges() {
|
||||
with (homeViewModel) {
|
||||
trending.observe(viewLifecycleOwner, ::showTrending)
|
||||
feed.observe(viewLifecycleOwner, ::showFeed)
|
||||
bookmarks.observe(viewLifecycleOwner, ::showBookmarks)
|
||||
playlists.observe(viewLifecycleOwner, ::showPlaylists)
|
||||
continueWatching.observe(viewLifecycleOwner, ::showContinueWatching)
|
||||
isLoading.observe(viewLifecycleOwner, ::updateLoading)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
stopObservingChanges()
|
||||
}
|
||||
|
||||
private fun stopObservingChanges() {
|
||||
with (homeViewModel) {
|
||||
trending.removeObserver(::showTrending)
|
||||
feed.removeObserver(::showFeed)
|
||||
bookmarks.removeObserver(::showBookmarks)
|
||||
playlists.removeObserver(::showPlaylists)
|
||||
continueWatching.removeObserver(::showContinueWatching)
|
||||
isLoading.removeObserver(::updateLoading)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@ -91,118 +127,58 @@ class HomeFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun fetchHomeFeed() {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
binding.nothingHere.isGone = true
|
||||
val defaultItems = resources.getStringArray(R.array.homeTabItemsValues)
|
||||
val visibleItems = PreferenceHelper
|
||||
.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() }
|
||||
)
|
||||
binding.nothingHere.isGone = true
|
||||
val defaultItems = resources.getStringArray(R.array.homeTabItemsValues)
|
||||
val visibleItems = PreferenceHelper.getStringSet(HOME_TAB_CONTENT, defaultItems.toSet())
|
||||
|
||||
val binding = _binding ?: return@repeatOnLifecycle
|
||||
// No category is shown because they are either empty or disabled
|
||||
if (binding.progress.isVisible) {
|
||||
binding.progress.isGone = true
|
||||
binding.nothingHere.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
homeViewModel.loadHomeFeed(
|
||||
context = requireContext(),
|
||||
savedFeed = subscriptionsViewModel.videoFeed.value,
|
||||
visibleItems = visibleItems,
|
||||
onUnusualLoadTime = ::showChangeInstanceSnackBar
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadTrending() {
|
||||
val region = LocaleHelper.getTrendingRegion(requireContext())
|
||||
val trending = runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
RetrofitInstance.api.getTrending(region).deArrow().take(10)
|
||||
}
|
||||
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
|
||||
val binding = _binding ?: return
|
||||
private fun showTrending(streamItems: List<StreamItem>?) {
|
||||
if (streamItems == null) return
|
||||
|
||||
makeVisible(binding.trendingRV, binding.trendingTV)
|
||||
binding.trendingRV.layoutManager = GridLayoutManager(context, 2)
|
||||
binding.trendingRV.adapter = VideosAdapter(
|
||||
trending.toMutableList(),
|
||||
forceMode = VideosAdapter.Companion.LayoutMode.TRENDING_ROW
|
||||
streamItems.toMutableList(),
|
||||
forceMode = LayoutMode.TRENDING_ROW
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadFeed() {
|
||||
val savedFeed = subscriptionsViewModel.videoFeed.value
|
||||
val feed = if (
|
||||
PreferenceHelper.getBoolean(PreferenceKeys.SAVE_FEED, false) &&
|
||||
!savedFeed.isNullOrEmpty()
|
||||
) {
|
||||
savedFeed
|
||||
} else {
|
||||
runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
SubscriptionHelper.getFeed()
|
||||
}
|
||||
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
|
||||
}
|
||||
|
||||
val allowShorts = ContentFilter.SHORTS.isEnabled()
|
||||
val allowVideos = ContentFilter.VIDEOS.isEnabled()
|
||||
val allowAll = (!allowShorts && !allowVideos)
|
||||
|
||||
var filteredFeed = feed.filter {
|
||||
(allowShorts && it.isShort) || (allowVideos && !it.isShort) || allowAll
|
||||
}
|
||||
if (PreferenceHelper.getBoolean(PreferenceKeys.HIDE_WATCHED_FROM_FEED, false)) {
|
||||
filteredFeed = runBlocking { DatabaseHelper.filterUnwatched(filteredFeed) }
|
||||
}
|
||||
val binding = _binding ?: return
|
||||
private fun showFeed(streamItems: List<StreamItem>?) {
|
||||
if (streamItems == null) return
|
||||
|
||||
makeVisible(binding.featuredRV, binding.featuredTV)
|
||||
binding.featuredRV.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
binding.featuredRV.adapter = VideosAdapter(
|
||||
filteredFeed.take(20).toMutableList(),
|
||||
forceMode = VideosAdapter.Companion.LayoutMode.RELATED_COLUMN
|
||||
)
|
||||
binding.featuredRV.setHasFixedSize(true)
|
||||
val feedVideos = streamItems.take(20).toMutableList()
|
||||
with (binding.featuredRV) {
|
||||
layoutManager = LinearLayoutManager(context, HORIZONTAL, false)
|
||||
adapter = VideosAdapter(feedVideos, forceMode = LayoutMode.RELATED_COLUMN)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadBookmarks() {
|
||||
val bookmarkedPlaylists = withContext(Dispatchers.IO) {
|
||||
DatabaseHolder.Database.playlistBookmarkDao().getAll()
|
||||
}.takeIf { it.isNotEmpty() } ?: return
|
||||
val binding = _binding ?: return
|
||||
private fun showBookmarks(bookmarks: List<PlaylistBookmark>?) {
|
||||
if (bookmarks == null) return
|
||||
|
||||
makeVisible(binding.bookmarksTV, binding.bookmarksRV)
|
||||
binding.bookmarksRV.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
binding.bookmarksRV.adapter = PlaylistBookmarkAdapter(
|
||||
bookmarkedPlaylists,
|
||||
PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME
|
||||
)
|
||||
with (binding.bookmarksRV) {
|
||||
layoutManager = LinearLayoutManager(context, HORIZONTAL, false)
|
||||
adapter = PlaylistBookmarkAdapter(bookmarks.toMutableList())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadPlaylists() {
|
||||
val playlists = runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
PlaylistsHelper.getPlaylists().take(20)
|
||||
}
|
||||
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
|
||||
val binding = _binding ?: return
|
||||
private fun showPlaylists(playlists: List<Playlists>?) {
|
||||
if (playlists == null) return
|
||||
|
||||
makeVisible(binding.playlistsRV, binding.playlistsTV)
|
||||
binding.playlistsRV.layoutManager = LinearLayoutManager(context)
|
||||
binding.playlistsRV.adapter = PlaylistsAdapter(
|
||||
playlists.toMutableList(),
|
||||
PlaylistsHelper.getPrivatePlaylistType()
|
||||
playlistType = PlaylistsHelper.getPrivatePlaylistType()
|
||||
)
|
||||
binding.playlistsRV.adapter?.registerAdapterDataObserver(object :
|
||||
RecyclerView.AdapterDataObserver() {
|
||||
@ -216,46 +192,72 @@ 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
|
||||
private fun showContinueWatching(unwatchedVideos: List<StreamItem>?) {
|
||||
if (unwatchedVideos == null) return
|
||||
|
||||
makeVisible(binding.watchingRV, binding.watchingTV)
|
||||
binding.watchingRV.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
binding.watchingRV.layoutManager = LinearLayoutManager(context, HORIZONTAL, false)
|
||||
binding.watchingRV.adapter = VideosAdapter(
|
||||
unwatchedVideos.toMutableList(),
|
||||
forceMode = VideosAdapter.Companion.LayoutMode.RELATED_COLUMN
|
||||
forceMode = LayoutMode.RELATED_COLUMN
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateLoading(isLoading: Boolean) {
|
||||
if (isLoading) {
|
||||
showLoading()
|
||||
} else {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLoading() {
|
||||
binding.progress.isVisible = !binding.refresh.isRefreshing
|
||||
binding.nothingHere.isVisible = false
|
||||
}
|
||||
|
||||
private fun hideLoading() {
|
||||
binding.progress.isVisible = false
|
||||
binding.refresh.isRefreshing = false
|
||||
|
||||
val hasContent = homeViewModel.loadedSuccessfully.value == true
|
||||
if (hasContent) {
|
||||
showContent()
|
||||
} else {
|
||||
showNothingHere()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showNothingHere() {
|
||||
binding.nothingHere.isVisible = true
|
||||
binding.scroll.isVisible = false
|
||||
}
|
||||
|
||||
private fun showContent() {
|
||||
binding.nothingHere.isVisible = false
|
||||
binding.scroll.isVisible = true
|
||||
}
|
||||
|
||||
private fun showChangeInstanceSnackBar() {
|
||||
val root = _binding?.root ?: return
|
||||
Snackbar
|
||||
.make(root, R.string.suggest_change_instance, Snackbar.LENGTH_LONG)
|
||||
.apply {
|
||||
setAction(R.string.change) {
|
||||
redirectToIntentSettings()
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun redirectToIntentSettings() {
|
||||
val settingsIntent = Intent(context, SettingsActivity::class.java).apply {
|
||||
putExtra(SettingsActivity.REDIRECT_KEY, SettingsActivity.REDIRECT_TO_INTENT_SETTINGS)
|
||||
}
|
||||
startActivity(settingsIntent)
|
||||
}
|
||||
|
||||
private fun makeVisible(vararg views: View) {
|
||||
views.forEach {
|
||||
it.isVisible = true
|
||||
}
|
||||
val binding = _binding ?: return
|
||||
binding.progress.isGone = true
|
||||
binding.scroll.isVisible = true
|
||||
binding.refresh.isRefreshing = false
|
||||
}
|
||||
|
||||
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"
|
||||
views.forEach { it.isVisible = true }
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package com.github.libretube.ui.models
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.libretube.api.PlaylistsHelper
|
||||
import com.github.libretube.api.RetrofitInstance
|
||||
import com.github.libretube.api.SubscriptionHelper
|
||||
import com.github.libretube.api.obj.Playlists
|
||||
import com.github.libretube.api.obj.StreamItem
|
||||
import com.github.libretube.constants.PreferenceKeys.HIDE_WATCHED_FROM_FEED
|
||||
import com.github.libretube.constants.PreferenceKeys.SAVE_FEED
|
||||
import com.github.libretube.db.DatabaseHelper
|
||||
import com.github.libretube.db.DatabaseHolder
|
||||
import com.github.libretube.db.obj.PlaylistBookmark
|
||||
import com.github.libretube.enums.ContentFilter
|
||||
import com.github.libretube.extensions.runSafely
|
||||
import com.github.libretube.extensions.updateIfChanged
|
||||
import com.github.libretube.helpers.LocaleHelper
|
||||
import com.github.libretube.helpers.PlayerHelper
|
||||
import com.github.libretube.helpers.PreferenceHelper
|
||||
import com.github.libretube.util.deArrow
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class HomeViewModel: ViewModel() {
|
||||
|
||||
private val useSavedFeed get() = PreferenceHelper.getBoolean(SAVE_FEED, false)
|
||||
private val hideWatched get() = PreferenceHelper.getBoolean(HIDE_WATCHED_FROM_FEED, false)
|
||||
|
||||
val trending: MutableLiveData<List<StreamItem>> = MutableLiveData(null)
|
||||
|
||||
val feed: MutableLiveData<List<StreamItem>> = MutableLiveData(null)
|
||||
|
||||
val bookmarks: MutableLiveData<List<PlaylistBookmark>> = MutableLiveData(null)
|
||||
|
||||
val playlists: MutableLiveData<List<Playlists>> = MutableLiveData(null)
|
||||
|
||||
val continueWatching: MutableLiveData<List<StreamItem>> = MutableLiveData(null)
|
||||
|
||||
val isLoading: MutableLiveData<Boolean> = MutableLiveData(true)
|
||||
|
||||
val loadedSuccessfully: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||
|
||||
private var loadHomeJob: Job? = null
|
||||
|
||||
fun loadHomeFeed(
|
||||
context: Context,
|
||||
savedFeed: List<StreamItem>? = null,
|
||||
visibleItems: Set<String>,
|
||||
onUnusualLoadTime: () -> Unit
|
||||
) {
|
||||
isLoading.value = true
|
||||
|
||||
loadHomeJob?.cancel()
|
||||
loadHomeJob = viewModelScope.launch {
|
||||
val result = async {
|
||||
awaitAll(
|
||||
async { if (visibleItems.contains(TRENDING)) loadTrending(context) },
|
||||
async { if (visibleItems.contains(FEATURED)) loadFeed(savedFeed) },
|
||||
async { if (visibleItems.contains(BOOKMARKS)) loadBookmarks() },
|
||||
async { if (visibleItems.contains(PLAYLISTS)) loadPlaylists() },
|
||||
async { if (visibleItems.contains(WATCHING)) loadVideosToContinueWatching() }
|
||||
)
|
||||
loadedSuccessfully.value = trending.value.isNullOrEmpty() == false
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
delay(UNUSUAL_LOAD_TIME_MS)
|
||||
if (result.isActive) {
|
||||
onUnusualLoadTime.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private suspend fun loadTrending(context: Context) {
|
||||
val region = LocaleHelper.getTrendingRegion(context)
|
||||
|
||||
runSafely(
|
||||
onSuccess = { videos -> trending.updateIfChanged(videos) },
|
||||
ioBlock = { RetrofitInstance.api.getTrending(region).deArrow().take(10) }
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadFeed(savedFeed: List<StreamItem>? = null) {
|
||||
runSafely(
|
||||
onSuccess = { videos -> feed.updateIfChanged(videos) },
|
||||
ioBlock = { tryLoadFeed(savedFeed) }
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadBookmarks() {
|
||||
runSafely(
|
||||
onSuccess = { newBookmarks -> bookmarks.updateIfChanged(newBookmarks) },
|
||||
ioBlock = { DatabaseHolder.Database.playlistBookmarkDao().getAll() }
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadPlaylists() {
|
||||
runSafely(
|
||||
onSuccess = { newPlaylists -> playlists.updateIfChanged(newPlaylists) },
|
||||
ioBlock = { PlaylistsHelper.getPlaylists().take(20) }
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadVideosToContinueWatching() {
|
||||
if (!PlayerHelper.watchHistoryEnabled) return
|
||||
runSafely(
|
||||
onSuccess = { videos -> continueWatching.updateIfChanged(videos) },
|
||||
ioBlock = ::loadWatchingFromDB
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadWatchingFromDB(): List<StreamItem> {
|
||||
val videos = DatabaseHolder.Database.watchHistoryDao().getAll()
|
||||
return DatabaseHelper
|
||||
.filterUnwatched(videos.map { it.toStreamItem() })
|
||||
.reversed()
|
||||
.take(20)
|
||||
}
|
||||
|
||||
private suspend fun tryLoadFeed(savedFeed: List<StreamItem>?): List<StreamItem> {
|
||||
val feed = if (useSavedFeed && !savedFeed.isNullOrEmpty()) {
|
||||
savedFeed
|
||||
} else {
|
||||
SubscriptionHelper.getFeed()
|
||||
}
|
||||
|
||||
return if (hideWatched) feed.filterWatched() else feed
|
||||
}
|
||||
|
||||
private suspend fun List<StreamItem>.filterWatched(): List<StreamItem> {
|
||||
val allowShorts = ContentFilter.SHORTS.isEnabled()
|
||||
val allowVideos = ContentFilter.VIDEOS.isEnabled()
|
||||
val allowAll = (!allowShorts && !allowVideos)
|
||||
|
||||
val filteredFeed = this.filter {
|
||||
allowAll || (allowShorts && it.isShort) || (allowVideos && !it.isShort)
|
||||
}
|
||||
return runBlocking { DatabaseHelper.filterUnwatched(filteredFeed) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val UNUSUAL_LOAD_TIME_MS = 10000L
|
||||
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"
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
@ -126,6 +127,26 @@
|
||||
android:text="@string/emptyList"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/refresh_button"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="30dp"
|
||||
android:textSize="12sp"
|
||||
android:text="@string/retry"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/change_instance"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="5dp"
|
||||
android:textSize="12sp"
|
||||
android:text="@string/change_instance"/>
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
@ -301,6 +301,8 @@
|
||||
<string name="play_next">Play next</string>
|
||||
<string name="navigation_bar">Navigation bar</string>
|
||||
<string name="change_region">Trending seems to be unavailable for the current region. Please select another in the settings.</string>
|
||||
<string name="change_instance">Change instance</string>
|
||||
<string name="suggest_change_instance">Loading is taking more than usual. Consider changing instance</string>
|
||||
<string name="playback_pitch">Pitch</string>
|
||||
<string name="filename">Filename</string>
|
||||
<string name="invalid_filename">Invalid filename!</string>
|
||||
@ -504,6 +506,7 @@
|
||||
<string name="invalid_input">Invalid input</string>
|
||||
<string name="add_to_group">Add to group</string>
|
||||
<string name="uptime">%.2f%% uptime</string>
|
||||
<string name="change">Change</string>
|
||||
|
||||
<!-- Notification channel strings -->
|
||||
<string name="download_channel_name">Download Service</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user