Merge pull request #3182 from Isira-Seneviratne/Remove_BaseFragment

Remove BaseFragment.
This commit is contained in:
Bnyro 2023-02-26 15:39:25 +01:00 committed by GitHub
commit 9a659d7a03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 275 additions and 322 deletions

View File

@ -1,14 +0,0 @@
package com.github.libretube.extensions
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
fun Fragment.launchWhenCreatedIO(block: suspend () -> Unit) {
lifecycleScope.launchWhenCreated {
withContext(Dispatchers.IO) {
block.invoke()
}
}
}

View File

@ -1,10 +0,0 @@
package com.github.libretube.ui.base
import androidx.fragment.app.Fragment
open class BaseFragment : Fragment() {
fun runOnUiThread(action: () -> Unit) {
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
}

View File

@ -13,6 +13,7 @@ import android.text.format.DateUtils
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.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.StreamItem
@ -27,7 +28,6 @@ import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.obj.ShareData import com.github.libretube.obj.ShareData
import com.github.libretube.services.BackgroundMode import com.github.libretube.services.BackgroundMode
import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.interfaces.AudioPlayerOptions import com.github.libretube.ui.interfaces.AudioPlayerOptions
import com.github.libretube.ui.listeners.AudioPlayerThumbnailListener import com.github.libretube.ui.listeners.AudioPlayerThumbnailListener
@ -36,7 +36,7 @@ import com.github.libretube.ui.sheets.PlayingQueueSheet
import com.github.libretube.ui.sheets.VideoOptionsBottomSheet import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
class AudioPlayerFragment : BaseFragment(), AudioPlayerOptions { class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
private lateinit var binding: FragmentAudioPlayerBinding private lateinit var binding: FragmentAudioPlayerBinding
private lateinit var audioHelper: AudioHelper private lateinit var audioHelper: AudioHelper

View File

@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.view.children import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R import com.github.libretube.R
@ -24,7 +25,6 @@ import com.github.libretube.obj.ChannelTabs
import com.github.libretube.obj.ShareData import com.github.libretube.obj.ShareData
import com.github.libretube.ui.adapters.SearchAdapter import com.github.libretube.ui.adapters.SearchAdapter
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.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.extensions.setupSubscriptionButton import com.github.libretube.ui.extensions.setupSubscriptionButton
import java.io.IOException import java.io.IOException
@ -33,7 +33,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import retrofit2.HttpException import retrofit2.HttpException
class ChannelFragment : BaseFragment() { class ChannelFragment : Fragment() {
private lateinit var binding: FragmentChannelBinding private lateinit var binding: FragmentChannelBinding
private var channelId: String? = null private var channelId: String? = null
@ -102,10 +102,12 @@ class ChannelFragment : BaseFragment() {
private fun fetchChannel() { private fun fetchChannel() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
if (channelId != null) { withContext(Dispatchers.IO) {
RetrofitInstance.api.getChannel(channelId!!) if (channelId != null) {
} else { RetrofitInstance.api.getChannel(channelId!!)
RetrofitInstance.api.getChannelByName(channelName!!) } else {
RetrofitInstance.api.getChannelByName(channelName!!)
}
} }
} catch (e: IOException) { } catch (e: IOException) {
binding.channelRefresh.isRefreshing = false binding.channelRefresh.isRefreshing = false
@ -129,64 +131,60 @@ class ChannelFragment : BaseFragment() {
isSubscribed = SubscriptionHelper.isSubscribed(channelId!!) isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
if (isSubscribed == null) return@launchWhenCreated if (isSubscribed == null) return@launchWhenCreated
runOnUiThread { binding.channelSubscribe.setupSubscriptionButton(
binding.channelSubscribe.setupSubscriptionButton( channelId,
channelId, channelName,
channelName, binding.notificationBell
binding.notificationBell )
)
binding.channelShare.setOnClickListener { binding.channelShare.setOnClickListener {
val shareDialog = ShareDialog( val shareDialog = ShareDialog(
response.id!!.toID(), response.id!!.toID(),
ShareObjectType.CHANNEL, ShareObjectType.CHANNEL,
shareData shareData
) )
shareDialog.show(childFragmentManager, ShareDialog::class.java.name) shareDialog.show(childFragmentManager, ShareDialog::class.java.name)
}
} }
nextPage = response.nextpage nextPage = response.nextpage
isLoading = false isLoading = false
binding.channelRefresh.isRefreshing = false binding.channelRefresh.isRefreshing = false
runOnUiThread { binding.channelScrollView.visibility = View.VISIBLE
binding.channelScrollView.visibility = View.VISIBLE binding.channelName.text = response.name
binding.channelName.text = response.name if (response.verified) {
if (response.verified) { binding.channelName.setCompoundDrawablesWithIntrinsicBounds(
binding.channelName.setCompoundDrawablesWithIntrinsicBounds( 0,
0, 0,
0, R.drawable.ic_verified,
R.drawable.ic_verified, 0
0
)
}
binding.channelSubs.text = resources.getString(
R.string.subscribers,
response.subscriberCount.formatShort()
) )
if (response.description.isBlank()) {
binding.channelDescription.visibility = View.GONE
} else {
binding.channelDescription.text = response.description.trim()
}
binding.channelDescription.setOnClickListener {
(it as TextView).apply {
it.maxLines = if (it.maxLines == Int.MAX_VALUE) 2 else Int.MAX_VALUE
}
}
ImageHelper.loadImage(response.bannerUrl, binding.channelBanner)
ImageHelper.loadImage(response.avatarUrl, binding.channelImage)
// recyclerview of the videos by the channel
channelAdapter = VideosAdapter(
response.relatedStreams.toMutableList(),
forceMode = VideosAdapter.Companion.ForceMode.CHANNEL
)
binding.channelRecView.adapter = channelAdapter
} }
binding.channelSubs.text = resources.getString(
R.string.subscribers,
response.subscriberCount.formatShort()
)
if (response.description.isBlank()) {
binding.channelDescription.visibility = View.GONE
} else {
binding.channelDescription.text = response.description.trim()
}
binding.channelDescription.setOnClickListener {
(it as TextView).apply {
it.maxLines = if (it.maxLines == Int.MAX_VALUE) 2 else Int.MAX_VALUE
}
}
ImageHelper.loadImage(response.bannerUrl, binding.channelBanner)
ImageHelper.loadImage(response.avatarUrl, binding.channelImage)
// recyclerview of the videos by the channel
channelAdapter = VideosAdapter(
response.relatedStreams.toMutableList(),
forceMode = VideosAdapter.Companion.ForceMode.CHANNEL
)
binding.channelRecView.adapter = channelAdapter
setupTabs(response.tabs) setupTabs(response.tabs)
} }

View File

@ -9,6 +9,7 @@ import android.os.IBinder
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.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -22,7 +23,6 @@ import com.github.libretube.obj.DownloadStatus
import com.github.libretube.receivers.DownloadReceiver import com.github.libretube.receivers.DownloadReceiver
import com.github.libretube.services.DownloadService import com.github.libretube.services.DownloadService
import com.github.libretube.ui.adapters.DownloadsAdapter import com.github.libretube.ui.adapters.DownloadsAdapter
import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.viewholders.DownloadsViewHolder import com.github.libretube.ui.viewholders.DownloadsViewHolder
import java.io.File import java.io.File
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class DownloadsFragment : BaseFragment() { class DownloadsFragment : Fragment() {
private lateinit var binding: FragmentDownloadsBinding private lateinit var binding: FragmentDownloadsBinding
private var binder: DownloadService.LocalBinder? = null private var binder: DownloadService.LocalBinder? = null
private val downloads = mutableListOf<DownloadWithItems>() private val downloads = mutableListOf<DownloadWithItems>()

View File

@ -4,6 +4,7 @@ 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.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -17,19 +18,16 @@ 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.DatabaseHolder import com.github.libretube.db.DatabaseHolder
import com.github.libretube.extensions.launchWhenCreatedIO
import com.github.libretube.helpers.LocaleHelper import com.github.libretube.helpers.LocaleHelper
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
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.models.SubscriptionsViewModel import com.github.libretube.ui.models.SubscriptionsViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class HomeFragment : BaseFragment() { class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding private lateinit var binding: FragmentHomeBinding
private val subscriptionsViewModel: SubscriptionsViewModel by activityViewModels() private val subscriptionsViewModel: SubscriptionsViewModel by activityViewModels()
@ -70,11 +68,11 @@ class HomeFragment : BaseFragment() {
} }
private fun fetchHomeFeed() { private fun fetchHomeFeed() {
launchWhenCreatedIO { lifecycleScope.launchWhenCreated {
loadTrending() loadTrending()
loadBookmarks() loadBookmarks()
} }
launchWhenCreatedIO { lifecycleScope.launchWhenCreated {
loadFeed() loadFeed()
loadPlaylists() loadPlaylists()
} }
@ -83,89 +81,86 @@ class HomeFragment : BaseFragment() {
private suspend fun loadTrending() { private suspend fun loadTrending() {
val region = LocaleHelper.getTrendingRegion(requireContext()) val region = LocaleHelper.getTrendingRegion(requireContext())
val trending = runCatching { val trending = runCatching {
RetrofitInstance.api.getTrending(region).take(10) withContext(Dispatchers.IO) {
}.getOrNull().takeIf { it?.isNotEmpty() == true } ?: return RetrofitInstance.api.getTrending(region).take(10)
}
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
runOnUiThread { makeVisible(binding.trendingRV, binding.trendingTV)
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( trending.toMutableList(),
trending.toMutableList(), forceMode = VideosAdapter.Companion.ForceMode.TRENDING
forceMode = VideosAdapter.Companion.ForceMode.TRENDING )
)
}
} }
private suspend fun loadFeed() { private suspend fun loadFeed() {
val savedFeed = withContext(Dispatchers.Main) { val savedFeed = subscriptionsViewModel.videoFeed.value
subscriptionsViewModel.videoFeed.value
}
val feed = if ( val feed = if (
PreferenceHelper.getBoolean(PreferenceKeys.SAVE_FEED, false) && PreferenceHelper.getBoolean(PreferenceKeys.SAVE_FEED, false) &&
!savedFeed.isNullOrEmpty() !savedFeed.isNullOrEmpty()
) { savedFeed } else { ) { savedFeed } else {
runCatching { runCatching {
SubscriptionHelper.getFeed() withContext(Dispatchers.IO) {
SubscriptionHelper.getFeed()
}
}.getOrElse { return } }.getOrElse { return }
}.takeIf { it.isNotEmpty() }?.take(20) ?: return }.take(20)
runOnUiThread { makeVisible(binding.featuredRV, binding.featuredTV)
makeVisible(binding.featuredRV, binding.featuredTV) binding.featuredRV.layoutManager = LinearLayoutManager(
binding.featuredRV.layoutManager = LinearLayoutManager( context,
LinearLayoutManager.HORIZONTAL,
false
)
binding.featuredRV.adapter = VideosAdapter(
feed.toMutableList(),
forceMode = VideosAdapter.Companion.ForceMode.HOME
)
}
private suspend fun loadBookmarks() {
val bookmarkedPlaylists = withContext(Dispatchers.IO) {
DatabaseHolder.Database.playlistBookmarkDao().getAll()
}
if (bookmarkedPlaylists.isNotEmpty()) {
makeVisible(binding.bookmarksTV, binding.bookmarksRV)
binding.bookmarksRV.layoutManager = LinearLayoutManager(
context, context,
LinearLayoutManager.HORIZONTAL, LinearLayoutManager.HORIZONTAL,
false false
) )
binding.featuredRV.adapter = VideosAdapter( binding.bookmarksRV.adapter = PlaylistBookmarkAdapter(
feed.toMutableList(), bookmarkedPlaylists,
forceMode = VideosAdapter.Companion.ForceMode.HOME PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME
) )
} }
} }
private fun loadBookmarks() {
lifecycleScope.launch {
val bookmarkedPlaylists = withContext(Dispatchers.IO) {
DatabaseHolder.Database.playlistBookmarkDao().getAll()
}
if (bookmarkedPlaylists.isNotEmpty()) {
makeVisible(binding.bookmarksTV, binding.bookmarksRV)
binding.bookmarksRV.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
binding.bookmarksRV.adapter = PlaylistBookmarkAdapter(
bookmarkedPlaylists,
PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME
)
}
}
}
private suspend fun loadPlaylists() { private suspend fun loadPlaylists() {
val playlists = runCatching { val playlists = runCatching {
PlaylistsHelper.getPlaylists().take(20) withContext(Dispatchers.IO) {
}.getOrNull().takeIf { it?.isNotEmpty() == true } ?: return PlaylistsHelper.getPlaylists().take(20)
}
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
runOnUiThread { makeVisible(binding.playlistsRV, binding.playlistsTV)
makeVisible(binding.playlistsRV, binding.playlistsTV) binding.playlistsRV.layoutManager = LinearLayoutManager(context)
binding.playlistsRV.layoutManager = LinearLayoutManager(context) binding.playlistsRV.adapter = PlaylistsAdapter(
binding.playlistsRV.adapter = PlaylistsAdapter( playlists.toMutableList(),
playlists.toMutableList(), PlaylistsHelper.getPrivatePlaylistType()
PlaylistsHelper.getPrivatePlaylistType() )
) 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) { super.onItemRangeRemoved(positionStart, itemCount)
super.onItemRangeRemoved(positionStart, itemCount) if (itemCount == 0) {
if (itemCount == 0) { binding.playlistsRV.visibility = View.GONE
binding.playlistsRV.visibility = View.GONE binding.playlistsTV.visibility = View.GONE
binding.playlistsTV.visibility = View.GONE
}
} }
}) }
} })
} }
private fun makeVisible(vararg views: View) { private fun makeVisible(vararg views: View) {

View File

@ -9,6 +9,7 @@ import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -25,15 +26,13 @@ import com.github.libretube.helpers.NavBarHelper
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
import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.dialogs.CreatePlaylistDialog import com.github.libretube.ui.dialogs.CreatePlaylistDialog
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.PlayerViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class LibraryFragment : BaseFragment() { class LibraryFragment : Fragment() {
private lateinit var binding: FragmentLibraryBinding private lateinit var binding: FragmentLibraryBinding
private val playerViewModel: PlayerViewModel by activityViewModels() private val playerViewModel: PlayerViewModel by activityViewModels()
@ -118,7 +117,9 @@ class LibraryFragment : BaseFragment() {
binding.playlistRefresh.isRefreshing = true binding.playlistRefresh.isRefreshing = true
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
var playlists = try { var playlists = try {
PlaylistsHelper.getPlaylists() withContext(Dispatchers.IO) {
PlaylistsHelper.getPlaylists()
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG(), e.toString()) Log.e(TAG(), e.toString())
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
@ -128,10 +129,7 @@ class LibraryFragment : BaseFragment() {
} }
if (playlists.isNotEmpty()) { if (playlists.isNotEmpty()) {
playlists = when ( playlists = when (
PreferenceHelper.getString( PreferenceHelper.getString(PreferenceKeys.PLAYLISTS_ORDER, "recent")
PreferenceKeys.PLAYLISTS_ORDER,
"recent"
)
) { ) {
"recent" -> playlists "recent" -> playlists
"recent_reversed" -> playlists.reversed() "recent_reversed" -> playlists.reversed()
@ -158,9 +156,7 @@ class LibraryFragment : BaseFragment() {
binding.nothingHere.visibility = View.GONE binding.nothingHere.visibility = View.GONE
binding.playlistRecView.adapter = playlistsAdapter binding.playlistRecView.adapter = playlistsAdapter
} else { } else {
runOnUiThread { binding.nothingHere.visibility = View.VISIBLE
binding.nothingHere.visibility = View.VISIBLE
}
} }
} }
} }

View File

@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
@ -27,7 +28,6 @@ import com.github.libretube.extensions.toID
import com.github.libretube.helpers.ImageHelper import com.github.libretube.helpers.ImageHelper
import com.github.libretube.helpers.NavigationHelper import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.ui.adapters.PlaylistAdapter import com.github.libretube.ui.adapters.PlaylistAdapter
import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
@ -35,8 +35,9 @@ import com.github.libretube.util.TextUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class PlaylistFragment : BaseFragment() { class PlaylistFragment : Fragment() {
private lateinit var binding: FragmentPlaylistBinding private lateinit var binding: FragmentPlaylistBinding
// general playlist information // general playlist information
@ -104,7 +105,9 @@ class PlaylistFragment : BaseFragment() {
binding.playlistScrollview.visibility = View.GONE binding.playlistScrollview.visibility = View.GONE
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
PlaylistsHelper.getPlaylist(playlistId!!) withContext(Dispatchers.IO) {
PlaylistsHelper.getPlaylist(playlistId!!)
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG(), e.toString()) Log.e(TAG(), e.toString())
return@launchWhenCreated return@launchWhenCreated
@ -114,154 +117,151 @@ class PlaylistFragment : BaseFragment() {
nextPage = response.nextpage nextPage = response.nextpage
playlistName = response.name playlistName = response.name
isLoading = false isLoading = false
runOnUiThread { ImageHelper.loadImage(response.thumbnailUrl, binding.thumbnail)
ImageHelper.loadImage(response.thumbnailUrl, binding.thumbnail) binding.playlistProgress.visibility = View.GONE
binding.playlistProgress.visibility = View.GONE binding.playlistName.text = response.name
binding.playlistName.text = response.name
binding.playlistName.setOnClickListener { binding.playlistName.setOnClickListener {
binding.playlistName.maxLines = binding.playlistName.maxLines =
if (binding.playlistName.maxLines == 2) Int.MAX_VALUE else 2 if (binding.playlistName.maxLines == 2) Int.MAX_VALUE else 2
} }
binding.playlistInfo.text = binding.playlistInfo.text =
(if (response.uploader != null) response.uploader + TextUtils.SEPARATOR else "") + (if (response.uploader != null) response.uploader + TextUtils.SEPARATOR else "") +
getString(R.string.videoCount, response.videos.toString()) getString(R.string.videoCount, response.videos.toString())
// show playlist options // show playlist options
binding.optionsMenu.setOnClickListener { binding.optionsMenu.setOnClickListener {
PlaylistOptionsBottomSheet(playlistId!!, playlistName ?: "", playlistType).show( PlaylistOptionsBottomSheet(playlistId!!, playlistName ?: "", playlistType).show(
childFragmentManager, childFragmentManager,
PlaylistOptionsBottomSheet::class.java.name PlaylistOptionsBottomSheet::class.java.name
) )
} }
binding.playAll.setOnClickListener { binding.playAll.setOnClickListener {
if (playlistFeed.isEmpty()) return@setOnClickListener if (playlistFeed.isEmpty()) return@setOnClickListener
NavigationHelper.navigateVideo(
requireContext(),
response.relatedStreams.first().url?.toID(),
playlistId
)
}
if (playlistType == PlaylistType.PUBLIC) {
binding.bookmark.setOnClickListener {
isBookmarked = !isBookmarked
updateBookmarkRes()
lifecycleScope.launch(Dispatchers.IO) {
if (!isBookmarked) {
DatabaseHolder.Database.playlistBookmarkDao()
.deleteById(playlistId!!)
} else {
DatabaseHolder.Database.playlistBookmarkDao()
.insertAll(listOf(response.toPlaylistBookmark(playlistId!!)))
}
}
}
} else {
// private playlist, means shuffle is possible because all videos are received at once
binding.bookmark.setIconResource(R.drawable.ic_shuffle)
binding.bookmark.text = getString(R.string.shuffle)
binding.bookmark.setOnClickListener {
val queue = playlistFeed.shuffled()
PlayingQueue.resetToDefaults()
PlayingQueue.add(*queue.toTypedArray())
NavigationHelper.navigateVideo( NavigationHelper.navigateVideo(
requireContext(), requireContext(),
response.relatedStreams.first().url?.toID(), queue.first().url?.toID(),
playlistId playlistId = playlistId,
keepQueue = true
) )
} }
}
if (playlistType == PlaylistType.PUBLIC) { playlistAdapter = PlaylistAdapter(
binding.bookmark.setOnClickListener { playlistFeed,
isBookmarked = !isBookmarked playlistId!!,
updateBookmarkRes() playlistType
lifecycleScope.launch(Dispatchers.IO) { )
if (!isBookmarked) {
DatabaseHolder.Database.playlistBookmarkDao() // listen for playlist items to become deleted
.deleteById(playlistId!!) playlistAdapter!!.registerAdapterDataObserver(object :
} else { RecyclerView.AdapterDataObserver() {
DatabaseHolder.Database.playlistBookmarkDao() override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
.insertAll(listOf(response.toPlaylistBookmark(playlistId!!))) if (positionStart == 0) {
} ImageHelper.loadImage(
} playlistFeed.firstOrNull()?.thumbnail ?: "",
} binding.thumbnail
} else {
// private playlist, means shuffle is possible because all videos are received at once
binding.bookmark.setIconResource(R.drawable.ic_shuffle)
binding.bookmark.text = getString(R.string.shuffle)
binding.bookmark.setOnClickListener {
if (playlistFeed.isEmpty()) return@setOnClickListener
val queue = playlistFeed.shuffled()
PlayingQueue.resetToDefaults()
PlayingQueue.add(*queue.toTypedArray())
NavigationHelper.navigateVideo(
requireContext(),
queue.first().url?.toID(),
playlistId = playlistId,
keepQueue = true
) )
} }
}
playlistAdapter = PlaylistAdapter( val info = binding.playlistInfo.text.split(TextUtils.SEPARATOR)
playlistFeed, binding.playlistInfo.text = (
playlistId!!,
playlistType
)
// listen for playlist items to become deleted
playlistAdapter!!.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
if (positionStart == 0) {
ImageHelper.loadImage(
playlistFeed.firstOrNull()?.thumbnail ?: "",
binding.thumbnail
)
}
val info = binding.playlistInfo.text.split(TextUtils.SEPARATOR)
binding.playlistInfo.text = (
if (info.size == 2) { if (info.size == 2) {
info[0] + TextUtils.SEPARATOR info[0] + TextUtils.SEPARATOR
} else { } else {
"" ""
} }
) + getString( ) + getString(
R.string.videoCount, R.string.videoCount,
playlistAdapter!!.itemCount.toString() playlistAdapter!!.itemCount.toString()
) )
super.onItemRangeRemoved(positionStart, itemCount) super.onItemRangeRemoved(positionStart, itemCount)
} }
}) })
binding.playlistRecView.adapter = playlistAdapter binding.playlistRecView.adapter = playlistAdapter
binding.playlistScrollview.viewTreeObserver binding.playlistScrollview.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (!binding.playlistScrollview.canScrollVertically(1)) { if (!binding.playlistScrollview.canScrollVertically(1)) {
if (isLoading) return@addOnScrollChangedListener if (isLoading) return@addOnScrollChangedListener
// append more playlists to the recycler view // append more playlists to the recycler view
if (playlistType != PlaylistType.PUBLIC) { if (playlistType != PlaylistType.PUBLIC) {
isLoading = true isLoading = true
playlistAdapter?.showMoreItems() playlistAdapter?.showMoreItems()
isLoading = false isLoading = false
} else { } else {
fetchNextPage() fetchNextPage()
}
} }
} }
// listener for swiping to the left or right
if (playlistType != PlaylistType.PUBLIC) {
val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(
0,
ItemTouchHelper.LEFT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int
) {
val position = viewHolder.absoluteAdapterPosition
playlistAdapter!!.removeFromPlaylist(requireContext(), position)
}
}
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
itemTouchHelper.attachToRecyclerView(binding.playlistRecView)
} }
// listener for swiping to the left or right
if (playlistType != PlaylistType.PUBLIC) {
val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(
0,
ItemTouchHelper.LEFT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int
) {
val position = viewHolder.absoluteAdapterPosition
playlistAdapter!!.removeFromPlaylist(requireContext(), position)
}
}
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
itemTouchHelper.attachToRecyclerView(binding.playlistRecView)
}
lifecycleScope.launch(Dispatchers.IO) {
// update the playlist thumbnail if bookmarked // update the playlist thumbnail if bookmarked
lifecycleScope.launch(Dispatchers.IO) { val playlistBookmark = DatabaseHolder.Database.playlistBookmarkDao().getAll()
val playlistBookmark = DatabaseHolder.Database.playlistBookmarkDao().getAll() .firstOrNull { it.playlistId == playlistId }
.firstOrNull { it.playlistId == playlistId } playlistBookmark?.let {
playlistBookmark?.let { if (it.thumbnailUrl != response.thumbnailUrl) {
if (it.thumbnailUrl != response.thumbnailUrl) { it.thumbnailUrl = response.thumbnailUrl
it.thumbnailUrl = response.thumbnailUrl DatabaseHolder.Database.playlistBookmarkDao().update(it)
DatabaseHolder.Database.playlistBookmarkDao().update(it)
}
} }
} }
} }

View File

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -19,13 +20,12 @@ import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.ui.adapters.LegacySubscriptionAdapter import com.github.libretube.ui.adapters.LegacySubscriptionAdapter
import com.github.libretube.ui.adapters.SubscriptionChannelAdapter import com.github.libretube.ui.adapters.SubscriptionChannelAdapter
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.models.SubscriptionsViewModel import com.github.libretube.ui.models.SubscriptionsViewModel
import com.github.libretube.ui.sheets.BaseBottomSheet import com.github.libretube.ui.sheets.BaseBottomSheet
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class SubscriptionsFragment : BaseFragment() { class SubscriptionsFragment : Fragment() {
private lateinit var binding: FragmentSubscriptionsBinding private lateinit var binding: FragmentSubscriptionsBinding
private val viewModel: SubscriptionsViewModel by activityViewModels() private val viewModel: SubscriptionsViewModel by activityViewModels()

View File

@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
@ -15,12 +16,13 @@ import com.github.libretube.extensions.TAG
import com.github.libretube.helpers.LocaleHelper import com.github.libretube.helpers.LocaleHelper
import com.github.libretube.ui.activities.SettingsActivity import com.github.libretube.ui.activities.SettingsActivity
import com.github.libretube.ui.adapters.VideosAdapter import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.base.BaseFragment
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException import java.io.IOException
import retrofit2.HttpException import retrofit2.HttpException
class TrendsFragment : BaseFragment() { class TrendsFragment : Fragment() {
private lateinit var binding: FragmentTrendsBinding private lateinit var binding: FragmentTrendsBinding
override fun onCreateView( override fun onCreateView(
@ -45,9 +47,11 @@ class TrendsFragment : BaseFragment() {
private fun fetchTrending() { private fun fetchTrending() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getTrending( withContext(Dispatchers.IO) {
LocaleHelper.getTrendingRegion(requireContext()) RetrofitInstance.api.getTrending(
) LocaleHelper.getTrendingRegion(requireContext())
)
}
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG(), "IOException, you might not have internet connection") Log.e(TAG(), "IOException, you might not have internet connection")
@ -60,36 +64,20 @@ class TrendsFragment : BaseFragment() {
} finally { } finally {
binding.homeRefresh.isRefreshing = false binding.homeRefresh.isRefreshing = false
} }
runOnUiThread { binding.progressBar.visibility = View.GONE
binding.progressBar.visibility = View.GONE
// show a [SnackBar] if there are no trending videos available // show a [SnackBar] if there are no trending videos available
if (response.isEmpty()) { if (response.isEmpty()) {
Snackbar.make( Snackbar.make(binding.root, R.string.change_region, Snackbar.LENGTH_LONG)
binding.root, .setAction(R.string.settings) {
R.string.change_region, startActivity(Intent(context, SettingsActivity::class.java))
Snackbar.LENGTH_LONG }
) .show()
.setAction( return@launchWhenCreated
R.string.settings
) {
startActivity(
Intent(
context,
SettingsActivity::class.java
)
)
}
.show()
return@runOnUiThread
}
binding.recview.adapter = VideosAdapter(
response.toMutableList()
)
binding.recview.layoutManager = VideosAdapter.getLayout(requireContext())
} }
binding.recview.adapter = VideosAdapter(response.toMutableList())
binding.recview.layoutManager = VideosAdapter.getLayout(requireContext())
} }
} }
} }

View File

@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
@ -21,7 +22,6 @@ import com.github.libretube.extensions.dpToPx
import com.github.libretube.helpers.NavigationHelper import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.helpers.ProxyHelper import com.github.libretube.helpers.ProxyHelper
import com.github.libretube.ui.adapters.WatchHistoryAdapter import com.github.libretube.ui.adapters.WatchHistoryAdapter
import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -29,7 +29,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class WatchHistoryFragment : BaseFragment() { class WatchHistoryFragment : Fragment() {
private lateinit var binding: FragmentWatchHistoryBinding private lateinit var binding: FragmentWatchHistoryBinding
private val playerViewModel: PlayerViewModel by activityViewModels() private val playerViewModel: PlayerViewModel by activityViewModels()