Merge pull request #1895 from Bnyro/master

Improve the new home layout
This commit is contained in:
Bnyro 2022-11-18 16:24:21 +01:00 committed by GitHub
commit 594017f3a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 175 additions and 181 deletions

View File

@ -3,6 +3,8 @@ package com.github.libretube.api
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.api.obj.Subscription
import com.github.libretube.constants.PreferenceKeys import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.db.DatabaseHolder.Companion.Database import com.github.libretube.db.DatabaseHolder.Companion.Database
import com.github.libretube.db.obj.LocalSubscription import com.github.libretube.db.obj.LocalSubscription
@ -131,4 +133,28 @@ object SubscriptionHelper {
val localSubscriptions = getLocalSubscriptions() val localSubscriptions = getLocalSubscriptions()
return localSubscriptions.joinToString(",") { it.channelId } return localSubscriptions.joinToString(",") { it.channelId }
} }
suspend fun getSubscriptions(): List<Subscription> {
return if (PreferenceHelper.getToken() != "") {
RetrofitInstance.authApi.subscriptions(
PreferenceHelper.getToken()
)
} else {
RetrofitInstance.authApi.unauthenticatedSubscriptions(
getFormattedLocalSubscriptions()
)
}
}
suspend fun getFeed(): List<StreamItem> {
return if (PreferenceHelper.getToken() != "") {
RetrofitInstance.authApi.getFeed(
PreferenceHelper.getToken()
)
} else {
RetrofitInstance.authApi.getUnauthenticatedFeed(
getFormattedLocalSubscriptions()
)
}
}
} }

View File

@ -60,7 +60,7 @@ class VideosAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder {
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
return when { return when {
forceMode in listOf(ForceMode.TRENDING, ForceMode.RELATED) -> VideosViewHolder(TrendingRowBinding.inflate(layoutInflater, parent, false)) forceMode in listOf(ForceMode.TRENDING, ForceMode.RELATED, ForceMode.HOME) -> VideosViewHolder(TrendingRowBinding.inflate(layoutInflater, parent, false))
forceMode == ForceMode.CHANNEL -> VideosViewHolder(VideoRowBinding.inflate(layoutInflater, parent, false)) forceMode == ForceMode.CHANNEL -> VideosViewHolder(VideoRowBinding.inflate(layoutInflater, parent, false))
PreferenceHelper.getBoolean( PreferenceHelper.getBoolean(
PreferenceKeys.ALTERNATIVE_VIDEOS_LAYOUT, PreferenceKeys.ALTERNATIVE_VIDEOS_LAYOUT,
@ -83,11 +83,14 @@ class VideosAdapter(
// Trending layout // Trending layout
holder.trendingRowBinding?.apply { holder.trendingRowBinding?.apply {
if (forceMode == ForceMode.RELATED) { // set a fixed width for better visuals
val params = root.layoutParams val params = root.layoutParams
params.width = (180).toDp(root.context.resources).toInt() when (forceMode) {
root.layoutParams = params ForceMode.RELATED -> params.width = (180).toDp(root.context.resources).toInt()
ForceMode.HOME -> params.width = (250).toDp(root.context.resources).toInt()
else -> {}
} }
root.layoutParams = params
textViewTitle.text = video.title textViewTitle.text = video.title
textViewChannel.text = textViewChannel.text =
@ -168,7 +171,8 @@ class VideosAdapter(
TRENDING, TRENDING,
ROW, ROW,
CHANNEL, CHANNEL,
RELATED RELATED,
HOME
} }
fun getLayout(context: Context): LayoutManager { fun getLayout(context: Context): LayoutManager {

View File

@ -4,25 +4,27 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.SubscriptionHelper
import com.github.libretube.databinding.FragmentHomeBinding import com.github.libretube.databinding.FragmentHomeBinding
import com.github.libretube.extensions.toastFromMainThread
import com.github.libretube.ui.adapters.PlaylistsAdapter import com.github.libretube.ui.adapters.PlaylistsAdapter
import com.github.libretube.ui.adapters.VideosAdapter import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.models.HomeModel import com.github.libretube.ui.extensions.withMaxSize
import com.github.libretube.util.LocaleHelper import com.github.libretube.util.LocaleHelper
import com.github.libretube.util.PreferenceHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class HomeFragment : BaseFragment() { class HomeFragment : BaseFragment() {
private lateinit var binding: FragmentHomeBinding private lateinit var binding: FragmentHomeBinding
private val viewModel: HomeModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -48,42 +50,55 @@ class HomeFragment : BaseFragment() {
findNavController().navigate(R.id.libraryFragment) findNavController().navigate(R.id.libraryFragment)
} }
binding.refresh.setOnRefreshListener {
binding.refresh.isRefreshing = true
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
viewModel.fetchHome(requireContext(), LocaleHelper.getTrendingRegion(requireContext())) fetchHome(LocaleHelper.getTrendingRegion(requireContext()))
}
} }
viewModel.feed.observe(viewLifecycleOwner) { lifecycleScope.launch(Dispatchers.IO) {
binding.featuredTV.visibility = View.VISIBLE fetchHome(LocaleHelper.getTrendingRegion(requireContext()))
binding.featuredRV.visibility = View.VISIBLE }
binding.progress.visibility = View.GONE }
private suspend fun fetchHome(trendingRegion: String) {
val token = PreferenceHelper.getToken()
runOrError {
val feed = SubscriptionHelper.getFeed().withMaxSize(20)
if (feed.isEmpty()) return@runOrError
runOnUiThread {
makeVisible(binding.featuredRV, binding.featuredTV)
binding.featuredRV.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) binding.featuredRV.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
binding.featuredRV.adapter = VideosAdapter( binding.featuredRV.adapter = VideosAdapter(
it.toMutableList(), feed.toMutableList(),
childFragmentManager, childFragmentManager,
forceMode = VideosAdapter.Companion.ForceMode.RELATED forceMode = VideosAdapter.Companion.ForceMode.HOME
) )
} }
}
viewModel.trending.observe(viewLifecycleOwner) { runOrError {
if (it.isEmpty()) return@observe val trending = RetrofitInstance.api.getTrending(trendingRegion).withMaxSize(10)
binding.trendingTV.visibility = View.VISIBLE if (trending.isEmpty()) return@runOrError
binding.trendingRV.visibility = View.VISIBLE runOnUiThread {
binding.progress.visibility = View.GONE makeVisible(binding.trendingRV, binding.trendingTV)
binding.trendingRV.layoutManager = GridLayoutManager(context, 2) binding.trendingRV.layoutManager = GridLayoutManager(context, 2)
binding.trendingRV.adapter = VideosAdapter( binding.trendingRV.adapter = VideosAdapter(
it.toMutableList(), trending.toMutableList(),
childFragmentManager, childFragmentManager,
forceMode = VideosAdapter.Companion.ForceMode.TRENDING forceMode = VideosAdapter.Companion.ForceMode.TRENDING
) )
} }
}
viewModel.playlists.observe(viewLifecycleOwner) { runOrError {
if (it.isEmpty()) return@observe val playlists = RetrofitInstance.authApi.getUserPlaylists(token).withMaxSize(20)
binding.playlistsRV.visibility = View.VISIBLE if (playlists.isEmpty()) return@runOrError
binding.playlistsTV.visibility = View.VISIBLE runOnUiThread {
binding.progress.visibility = View.GONE makeVisible(binding.playlistsRV, binding.playlistsTV)
binding.playlistsRV.layoutManager = LinearLayoutManager(context) binding.playlistsRV.layoutManager = LinearLayoutManager(context)
binding.playlistsRV.adapter = PlaylistsAdapter(it.toMutableList(), childFragmentManager) binding.playlistsRV.adapter = PlaylistsAdapter(playlists.toMutableList(), childFragmentManager)
binding.playlistsRV.adapter?.registerAdapterDataObserver(object : binding.playlistsRV.adapter?.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() { RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
@ -97,3 +112,23 @@ class HomeFragment : BaseFragment() {
} }
} }
} }
private fun runOrError(action: suspend () -> Unit) {
lifecycleScope.launch(Dispatchers.IO) {
try {
action.invoke()
} catch (e: Exception) {
e.localizedMessage?.let { context?.toastFromMainThread(it) }
}
}
}
private fun makeVisible(vararg views: View) {
views.forEach {
it.visibility = View.VISIBLE
}
binding.progress.visibility = View.GONE
binding.scroll.visibility = View.VISIBLE
binding.refresh.isRefreshing = false
}
}

View File

@ -1,57 +0,0 @@
package com.github.libretube.ui.models
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.extensions.toastFromMainThread
import com.github.libretube.ui.extensions.withMaxSize
import com.github.libretube.util.PreferenceHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class HomeModel : ViewModel() {
val feed = MutableLiveData<List<StreamItem>>()
var trending = MutableLiveData<List<StreamItem>>()
val playlists = MutableLiveData<List<Playlists>>()
suspend fun fetchHome(context: Context, trendingRegion: String) {
val token = PreferenceHelper.getToken()
val appContext = context.applicationContext
runOrError(appContext) {
if (trending.value.isNullOrEmpty()) {
trending.postValue(
RetrofitInstance.api.getTrending(trendingRegion).withMaxSize(10)
)
}
}
runOrError(appContext) {
if (feed.value.isNullOrEmpty()) {
feed.postValue(
RetrofitInstance.authApi.getFeed(token).withMaxSize(20)
)
}
}
runOrError(appContext) {
if (token == "" || !playlists.value.isNullOrEmpty()) return@runOrError
playlists.postValue(
RetrofitInstance.authApi.getUserPlaylists(token).withMaxSize(20)
)
}
}
private fun runOrError(context: Context, action: suspend () -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
try {
action.invoke()
} catch (e: Exception) {
e.localizedMessage?.let { context.toastFromMainThread(it) }
}
}
}
}

View File

@ -3,8 +3,9 @@ package com.github.libretube.ui.models
import android.util.Log import android.util.Log
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.SubscriptionHelper import com.github.libretube.api.SubscriptionHelper
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.api.obj.Subscription
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
@ -17,26 +18,18 @@ class SubscriptionsViewModel : ViewModel() {
value = false value = false
} }
var videoFeed = MutableLiveData<List<com.github.libretube.api.obj.StreamItem>?>().apply { var videoFeed = MutableLiveData<List<StreamItem>?>().apply {
value = null value = null
} }
var subscriptions = MutableLiveData<List<com.github.libretube.api.obj.Subscription>?>().apply { var subscriptions = MutableLiveData<List<Subscription>?>().apply {
value = null value = null
} }
fun fetchFeed() { fun fetchFeed() {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val videoFeed = try { val videoFeed = try {
if (PreferenceHelper.getToken() != "") { SubscriptionHelper.getFeed()
RetrofitInstance.authApi.getFeed(
PreferenceHelper.getToken()
)
} else {
RetrofitInstance.authApi.getUnauthenticatedFeed(
SubscriptionHelper.getFormattedLocalSubscriptions()
)
}
} catch (e: Exception) { } catch (e: Exception) {
errorResponse.postValue(true) errorResponse.postValue(true)
Log.e(TAG(), e.toString()) Log.e(TAG(), e.toString())
@ -53,15 +46,7 @@ class SubscriptionsViewModel : ViewModel() {
fun fetchSubscriptions() { fun fetchSubscriptions() {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val subscriptions = try { val subscriptions = try {
if (PreferenceHelper.getToken() != "") { SubscriptionHelper.getSubscriptions()
RetrofitInstance.authApi.subscriptions(
PreferenceHelper.getToken()
)
} else {
RetrofitInstance.authApi.unauthenticatedSubscriptions(
SubscriptionHelper.getFormattedLocalSubscriptions()
)
}
} catch (e: Exception) { } catch (e: Exception) {
errorResponse.postValue(true) errorResponse.postValue(true)
Log.e(TAG(), e.toString()) Log.e(TAG(), e.toString())

View File

@ -10,7 +10,6 @@ import androidx.core.app.NotificationManagerCompat
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.SubscriptionHelper import com.github.libretube.api.SubscriptionHelper
import com.github.libretube.constants.PUSH_CHANNEL_ID import com.github.libretube.constants.PUSH_CHANNEL_ID
import com.github.libretube.constants.PreferenceKeys import com.github.libretube.constants.PreferenceKeys
@ -82,16 +81,9 @@ class NotificationWorker(appContext: Context, parameters: WorkerParameters) :
private fun checkForNewStreams(): Boolean { private fun checkForNewStreams(): Boolean {
var success = true var success = true
val token = PreferenceHelper.getToken()
runBlocking { runBlocking {
val task = async { val task = async {
if (token != "") { SubscriptionHelper.getFeed()
RetrofitInstance.authApi.getFeed(token)
} else {
RetrofitInstance.authApi.getUnauthenticatedFeed(
SubscriptionHelper.getFormattedLocalSubscriptions()
)
}
} }
// fetch the users feed // fetch the users feed
val videoFeed = try { val videoFeed = try {

View File

@ -9,7 +9,14 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" /> android:layout_gravity="center" />
<com.github.libretube.ui.views.CustomSwipeToRefresh
android:id="@+id/refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ScrollView <ScrollView
android:id="@+id/scroll"
android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -68,4 +75,6 @@
</ScrollView> </ScrollView>
</com.github.libretube.ui.views.CustomSwipeToRefresh>
</FrameLayout> </FrameLayout>