diff --git a/app/src/main/java/com/github/libretube/api/PipedApi.kt b/app/src/main/java/com/github/libretube/api/PipedApi.kt index 03c1b8aa7..61f24d214 100644 --- a/app/src/main/java/com/github/libretube/api/PipedApi.kt +++ b/app/src/main/java/com/github/libretube/api/PipedApi.kt @@ -144,7 +144,7 @@ interface PipedApi { ): PlaylistId @GET("user/playlists") - suspend fun playlists(@Header("Authorization") token: String): List + suspend fun getUserPlaylists(@Header("Authorization") token: String): List @POST("user/playlists/rename") suspend fun renamePlaylist( diff --git a/app/src/main/java/com/github/libretube/extensions/ToastFromMainThread.kt b/app/src/main/java/com/github/libretube/extensions/ToastFromMainThread.kt index 9bad80911..bb823c410 100644 --- a/app/src/main/java/com/github/libretube/extensions/ToastFromMainThread.kt +++ b/app/src/main/java/com/github/libretube/extensions/ToastFromMainThread.kt @@ -14,3 +14,13 @@ fun Context.toastFromMainThread(stringId: Int) { ).show() } } + +fun Context.toastFromMainThread(text: String) { + Handler(Looper.getMainLooper()).post { + Toast.makeText( + this, + text, + Toast.LENGTH_SHORT + ).show() + } +} diff --git a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt index f4849930d..a3c6f7cf1 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt @@ -314,6 +314,8 @@ class MainActivity : BaseActivity() { when (intent?.getStringExtra("fragmentToOpen")) { "home" -> navController.navigate(R.id.homeFragment) + "trends" -> + navController.navigate(R.id.trendsFragment) "subscriptions" -> navController.navigate(R.id.subscriptionsFragment) "library" -> diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/AddToPlaylistDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/AddToPlaylistDialog.kt index 4c008e81d..bf86d8577 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/AddToPlaylistDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/AddToPlaylistDialog.kt @@ -50,7 +50,7 @@ class AddToPlaylistDialog : DialogFragment() { private fun fetchPlaylists() { lifecycleScope.launchWhenCreated { val response = try { - RetrofitInstance.authApi.playlists(token) + RetrofitInstance.authApi.getUserPlaylists(token) } catch (e: IOException) { println(e) Log.e(TAG(), "IOException, you might not have internet connection") diff --git a/app/src/main/java/com/github/libretube/ui/extensions/WithMaxSize.kt b/app/src/main/java/com/github/libretube/ui/extensions/WithMaxSize.kt new file mode 100644 index 000000000..4eec26c62 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/extensions/WithMaxSize.kt @@ -0,0 +1,5 @@ +package com.github.libretube.ui.extensions + +fun List.withMaxSize(maxSize: Int): List { + return this.filterIndexed { index, _ -> index < maxSize } +} 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 648bb4285..2cf8bf955 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 @@ -1,30 +1,28 @@ package com.github.libretube.ui.fragments -import android.content.Intent import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast +import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.github.libretube.R -import com.github.libretube.api.RetrofitInstance -import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.FragmentHomeBinding -import com.github.libretube.extensions.TAG -import com.github.libretube.ui.activities.SettingsActivity +import com.github.libretube.ui.adapters.PlaylistsAdapter import com.github.libretube.ui.adapters.VideosAdapter import com.github.libretube.ui.base.BaseFragment +import com.github.libretube.ui.models.HomeModel import com.github.libretube.util.LocaleHelper -import com.github.libretube.util.PreferenceHelper -import com.google.android.material.snackbar.Snackbar -import retrofit2.HttpException -import java.io.IOException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class HomeFragment : BaseFragment() { private lateinit var binding: FragmentHomeBinding - private lateinit var region: String + private val viewModel: HomeModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, @@ -37,71 +35,65 @@ class HomeFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val regionPref = PreferenceHelper.getString(PreferenceKeys.REGION, "sys") - // get the system default country if auto region selected - region = if (regionPref == "sys") { - LocaleHelper - .getDetectedCountry(requireContext(), "UK") - .uppercase() - } else { - regionPref + binding.featuredTV.setOnClickListener { + findNavController().navigate(R.id.subscriptionsFragment) } - fetchTrending() - binding.homeRefresh.isEnabled = true - binding.homeRefresh.setOnRefreshListener { - fetchTrending() + binding.trendingTV.setOnClickListener { + findNavController().navigate(R.id.trendsFragment) } - } - private fun fetchTrending() { - lifecycleScope.launchWhenCreated { - val response = try { - RetrofitInstance.api.getTrending(region) - } catch (e: IOException) { - println(e) - Log.e(TAG(), "IOException, you might not have internet connection") - Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show() - return@launchWhenCreated - } catch (e: HttpException) { - Log.e(TAG(), "HttpException, unexpected response") - Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show() - return@launchWhenCreated - } finally { - binding.homeRefresh.isRefreshing = false - } - runOnUiThread { - binding.progressBar.visibility = View.GONE + binding.playlistsTV.setOnClickListener { + findNavController().navigate(R.id.libraryFragment) + } - // show a [SnackBar] if there are no trending videos available - if (response.isEmpty()) { - Snackbar.make( - binding.root, - R.string.change_region, - Snackbar.LENGTH_LONG - ) - .setAction( - R.string.settings - ) { - startActivity( - Intent( - context, - SettingsActivity::class.java - ) - ) + lifecycleScope.launch(Dispatchers.IO) { + viewModel.fetchHome(requireContext(), LocaleHelper.getTrendingRegion(requireContext())) + } + + viewModel.feed.observe(viewLifecycleOwner) { + binding.featuredTV.visibility = View.VISIBLE + binding.featuredRV.visibility = View.VISIBLE + binding.progress.visibility = View.GONE + binding.featuredRV.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + binding.featuredRV.adapter = VideosAdapter( + it.toMutableList(), + childFragmentManager, + forceMode = VideosAdapter.Companion.ForceMode.RELATED + ) + } + + viewModel.trending.observe(viewLifecycleOwner) { + if (it.isEmpty()) return@observe + binding.trendingTV.visibility = View.VISIBLE + binding.trendingRV.visibility = View.VISIBLE + binding.progress.visibility = View.GONE + binding.trendingRV.layoutManager = GridLayoutManager(context, 2) + binding.trendingRV.adapter = VideosAdapter( + it.toMutableList(), + childFragmentManager, + forceMode = VideosAdapter.Companion.ForceMode.TRENDING + ) + } + + viewModel.playlists.observe(viewLifecycleOwner) { + if (it.isEmpty()) return@observe + binding.playlistsRV.visibility = View.VISIBLE + binding.playlistsTV.visibility = View.VISIBLE + binding.progress.visibility = View.GONE + binding.playlistsRV.layoutManager = LinearLayoutManager(context) + binding.playlistsRV.adapter = PlaylistsAdapter(it.toMutableList(), childFragmentManager) + binding.playlistsRV.adapter?.registerAdapterDataObserver(object : + RecyclerView.AdapterDataObserver() { + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + super.onItemRangeRemoved(positionStart, itemCount) + if (itemCount == 0) { + binding.playlistsRV.visibility = View.GONE + binding.playlistsTV.visibility = View.GONE } - .show() - return@runOnUiThread - } - - binding.recview.adapter = VideosAdapter( - response.toMutableList(), - childFragmentManager - ) - - binding.recview.layoutManager = VideosAdapter.getLayout(requireContext()) - } + } + }) } } } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt index 8b593feee..b1f99ca54 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt @@ -99,7 +99,7 @@ class LibraryFragment : BaseFragment() { binding.playlistRefresh.isRefreshing = true lifecycleScope.launchWhenCreated { var playlists = try { - RetrofitInstance.authApi.playlists(token) + RetrofitInstance.authApi.getUserPlaylists(token) } catch (e: IOException) { println(e) Log.e(TAG(), "IOException, you might not have internet connection") diff --git a/app/src/main/java/com/github/libretube/ui/fragments/TrendsFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/TrendsFragment.kt new file mode 100644 index 000000000..adc7b2251 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/fragments/TrendsFragment.kt @@ -0,0 +1,96 @@ +package com.github.libretube.ui.fragments + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.lifecycle.lifecycleScope +import com.github.libretube.R +import com.github.libretube.api.RetrofitInstance +import com.github.libretube.databinding.FragmentTrendsBinding +import com.github.libretube.extensions.TAG +import com.github.libretube.ui.activities.SettingsActivity +import com.github.libretube.ui.adapters.VideosAdapter +import com.github.libretube.ui.base.BaseFragment +import com.github.libretube.util.LocaleHelper +import com.google.android.material.snackbar.Snackbar +import retrofit2.HttpException +import java.io.IOException + +class TrendsFragment : BaseFragment() { + private lateinit var binding: FragmentTrendsBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentTrendsBinding.inflate(layoutInflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + fetchTrending() + binding.homeRefresh.isEnabled = true + binding.homeRefresh.setOnRefreshListener { + fetchTrending() + } + } + + private fun fetchTrending() { + lifecycleScope.launchWhenCreated { + val response = try { + RetrofitInstance.api.getTrending( + LocaleHelper.getTrendingRegion(requireContext()) + ) + } catch (e: IOException) { + println(e) + Log.e(TAG(), "IOException, you might not have internet connection") + Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show() + return@launchWhenCreated + } catch (e: HttpException) { + Log.e(TAG(), "HttpException, unexpected response") + Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show() + return@launchWhenCreated + } finally { + binding.homeRefresh.isRefreshing = false + } + runOnUiThread { + binding.progressBar.visibility = View.GONE + + // show a [SnackBar] if there are no trending videos available + if (response.isEmpty()) { + Snackbar.make( + binding.root, + R.string.change_region, + Snackbar.LENGTH_LONG + ) + .setAction( + R.string.settings + ) { + startActivity( + Intent( + context, + SettingsActivity::class.java + ) + ) + } + .show() + return@runOnUiThread + } + + binding.recview.adapter = VideosAdapter( + response.toMutableList(), + childFragmentManager + ) + + binding.recview.layoutManager = VideosAdapter.getLayout(requireContext()) + } + } + } +} diff --git a/app/src/main/java/com/github/libretube/ui/models/HomeModel.kt b/app/src/main/java/com/github/libretube/ui/models/HomeModel.kt new file mode 100644 index 000000000..d20232f32 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/models/HomeModel.kt @@ -0,0 +1,57 @@ +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>() + var trending = MutableLiveData>() + val playlists = MutableLiveData>() + + 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) } + } + } + } +} diff --git a/app/src/main/java/com/github/libretube/util/LocaleHelper.kt b/app/src/main/java/com/github/libretube/util/LocaleHelper.kt index 8a45258fd..017a96022 100644 --- a/app/src/main/java/com/github/libretube/util/LocaleHelper.kt +++ b/app/src/main/java/com/github/libretube/util/LocaleHelper.kt @@ -126,4 +126,16 @@ object LocaleHelper { } return locales } + + fun getTrendingRegion(context: Context): String { + val regionPref = PreferenceHelper.getString(PreferenceKeys.REGION, "sys") + + // get the system default country if auto region selected + return if (regionPref == "sys") { + getDetectedCountry(context, "UK") + .uppercase() + } else { + regionPref + } + } } diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 31af0267b..1d7d34639 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,35 +1,71 @@ - + android:layout_height="match_parent"> + android:layout_height="match_parent" + android:layout_gravity="center" /> - + android:layout_height="wrap_content"> - + - + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_trends.xml b/app/src/main/res/layout/fragment_trends.xml new file mode 100644 index 000000000..039e85007 --- /dev/null +++ b/app/src/main/res/layout/fragment_trends.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_menu.xml b/app/src/main/res/menu/bottom_menu.xml index e41131592..c24a7cc01 100644 --- a/app/src/main/res/menu/bottom_menu.xml +++ b/app/src/main/res/menu/bottom_menu.xml @@ -6,6 +6,12 @@ android:icon="@drawable/ic_home" android:title="@string/startpage" /> + + - - \ No newline at end of file diff --git a/app/src/main/res/navigation/nav.xml b/app/src/main/res/navigation/nav.xml index 1b311537d..d076b0d5a 100644 --- a/app/src/main/res/navigation/nav.xml +++ b/app/src/main/res/navigation/nav.xml @@ -4,12 +4,16 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav" app:startDestination="@id/homeFragment"> - + Auto Limit to runtime Open queue from notification + Trends + Featured + What\'s trending now Download Service diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml index a78db0a98..c4c8be643 100644 --- a/app/src/main/res/values/style.xml +++ b/app/src/main/res/values/style.xml @@ -206,4 +206,16 @@ ?android:attr/textColorPrimary + + \ No newline at end of file diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml index 8df6da32b..d18406579 100644 --- a/app/src/main/res/xml/shortcuts.xml +++ b/app/src/main/res/xml/shortcuts.xml @@ -12,6 +12,16 @@ android:targetPackage="com.github.libretube" android:targetClass="com.github.libretube.ui.activities.MainActivity" /> + + +