mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 14:20:30 +05:30
Merge pull request #3648 from Bnyro/master
Sort menu for local and private playlist
This commit is contained in:
commit
9cb2cfd6aa
@ -38,6 +38,7 @@ object PreferenceKeys {
|
|||||||
const val ALTERNATIVE_VIDEOS_LAYOUT = "alternative_videos_layout"
|
const val ALTERNATIVE_VIDEOS_LAYOUT = "alternative_videos_layout"
|
||||||
const val NEW_VIDEOS_BADGE = "new_videos_badge"
|
const val NEW_VIDEOS_BADGE = "new_videos_badge"
|
||||||
const val PLAYLISTS_ORDER = "playlists_order"
|
const val PLAYLISTS_ORDER = "playlists_order"
|
||||||
|
const val PLAYLIST_SORT_ORDER = "playlist_sort_order"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instance
|
* Instance
|
||||||
|
@ -6,6 +6,7 @@ import android.util.Log
|
|||||||
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.core.view.isGone
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
@ -22,6 +23,7 @@ import com.github.libretube.api.RetrofitInstance
|
|||||||
import com.github.libretube.api.obj.Playlist
|
import com.github.libretube.api.obj.Playlist
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.github.libretube.databinding.FragmentPlaylistBinding
|
import com.github.libretube.databinding.FragmentPlaylistBinding
|
||||||
import com.github.libretube.db.DatabaseHolder
|
import com.github.libretube.db.DatabaseHolder
|
||||||
import com.github.libretube.enums.PlaylistType
|
import com.github.libretube.enums.PlaylistType
|
||||||
@ -31,8 +33,10 @@ import com.github.libretube.extensions.serializable
|
|||||||
import com.github.libretube.extensions.toID
|
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.helpers.PreferenceHelper
|
||||||
import com.github.libretube.ui.adapters.PlaylistAdapter
|
import com.github.libretube.ui.adapters.PlaylistAdapter
|
||||||
import com.github.libretube.ui.models.PlayerViewModel
|
import com.github.libretube.ui.models.PlayerViewModel
|
||||||
|
import com.github.libretube.ui.sheets.BaseBottomSheet
|
||||||
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
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -58,6 +62,11 @@ class PlaylistFragment : Fragment() {
|
|||||||
|
|
||||||
// view models
|
// view models
|
||||||
private val playerViewModel: PlayerViewModel by activityViewModels()
|
private val playerViewModel: PlayerViewModel by activityViewModels()
|
||||||
|
private var selectedSortOrder = PreferenceHelper.getInt(PreferenceKeys.PLAYLIST_SORT_ORDER, 0)
|
||||||
|
set(value) {
|
||||||
|
PreferenceHelper.putInt(PreferenceKeys.PLAYLIST_SORT_ORDER, value)
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -139,6 +148,8 @@ class PlaylistFragment : Fragment() {
|
|||||||
|
|
||||||
binding.playlistInfo.text = getChannelAndVideoString(response, response.videos)
|
binding.playlistInfo.text = getChannelAndVideoString(response, response.videos)
|
||||||
|
|
||||||
|
showPlaylistVideos(response)
|
||||||
|
|
||||||
// show playlist options
|
// show playlist options
|
||||||
binding.optionsMenu.setOnClickListener {
|
binding.optionsMenu.setOnClickListener {
|
||||||
PlaylistOptionsBottomSheet(
|
PlaylistOptionsBottomSheet(
|
||||||
@ -197,67 +208,17 @@ class PlaylistFragment : Fragment() {
|
|||||||
keepQueue = true
|
keepQueue = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
binding.sortMenu.isGone = false
|
||||||
|
binding.sortMenu.setOnClickListener {
|
||||||
|
val sortOptions = resources.getStringArray(R.array.playlistSortOptions)
|
||||||
|
|
||||||
playlistAdapter = PlaylistAdapter(playlistFeed, playlistId!!, playlistType)
|
BaseBottomSheet().apply {
|
||||||
|
setSimpleItems(sortOptions.toList()) { index ->
|
||||||
// listen for playlist items to become deleted
|
selectedSortOrder = index
|
||||||
playlistAdapter!!.registerAdapterDataObserver(object :
|
showPlaylistVideos(response)
|
||||||
RecyclerView.AdapterDataObserver() {
|
}
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
}.show(childFragmentManager)
|
||||||
if (positionStart == 0) {
|
|
||||||
ImageHelper.loadImage(
|
|
||||||
playlistFeed.firstOrNull()?.thumbnail ?: "",
|
|
||||||
binding.thumbnail
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.playlistInfo.text =
|
|
||||||
getChannelAndVideoString(response, playlistFeed.size)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
binding.playlistRecView.adapter = playlistAdapter
|
|
||||||
binding.playlistScrollview.viewTreeObserver.addOnScrollChangedListener {
|
|
||||||
if (_binding?.playlistScrollview?.canScrollVertically(1) == false &&
|
|
||||||
!isLoading
|
|
||||||
) {
|
|
||||||
// append more playlists to the recycler view
|
|
||||||
if (playlistType != PlaylistType.PUBLIC) {
|
|
||||||
isLoading = true
|
|
||||||
playlistAdapter?.showMoreItems()
|
|
||||||
isLoading = false
|
|
||||||
} else {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@ -275,6 +236,88 @@ class PlaylistFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showPlaylistVideos(playlist: Playlist) {
|
||||||
|
val videos = if (playlistType == PlaylistType.PUBLIC) playlistFeed
|
||||||
|
else {
|
||||||
|
when (selectedSortOrder) {
|
||||||
|
0, 1 -> {
|
||||||
|
if (playlistType == PlaylistType.LOCAL) playlistFeed.sortedBy {
|
||||||
|
it.url.orEmpty().toInt()
|
||||||
|
} else playlistFeed
|
||||||
|
}
|
||||||
|
2, 3 -> {
|
||||||
|
playlistFeed.sortedBy { it.duration }
|
||||||
|
}
|
||||||
|
4, 5 -> {
|
||||||
|
playlistFeed.sortedBy { it.title }
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException()
|
||||||
|
}.let {
|
||||||
|
if (selectedSortOrder % 2 == 0) it else it.reversed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playlistAdapter = PlaylistAdapter(videos.toMutableList(), playlistId!!, playlistType)
|
||||||
|
binding.playlistRecView.adapter = playlistAdapter
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.playlistInfo.text = getChannelAndVideoString(playlist, playlistFeed.size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.playlistScrollview.viewTreeObserver.addOnScrollChangedListener {
|
||||||
|
if (_binding?.playlistScrollview?.canScrollVertically(1) == false &&
|
||||||
|
!isLoading
|
||||||
|
) {
|
||||||
|
// append more playlists to the recycler view
|
||||||
|
if (playlistType != PlaylistType.PUBLIC) {
|
||||||
|
isLoading = true
|
||||||
|
playlistAdapter?.showMoreItems()
|
||||||
|
isLoading = false
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("StringFormatInvalid", "StringFormatMatches")
|
@SuppressLint("StringFormatInvalid", "StringFormatMatches")
|
||||||
private fun getChannelAndVideoString(playlist: Playlist, count: Int): String {
|
private fun getChannelAndVideoString(playlist: Playlist, count: Int): String {
|
||||||
return playlist.uploader?.let {
|
return playlist.uploader?.let {
|
||||||
|
10
app/src/main/res/drawable/ic_sort.xml
Normal file
10
app/src/main/res/drawable/ic_sort.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:width="24dp">
|
||||||
|
<path android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||||
|
</vector>
|
@ -53,6 +53,15 @@
|
|||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/sortMenu"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:src="@drawable/ic_sort"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/optionsMenu"
|
android:id="@+id/optionsMenu"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
|
@ -127,7 +127,7 @@
|
|||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:text="@string/most_recent"
|
android:text="@string/most_recent"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:drawableEndCompat="@drawable/ic_arrow_downward" />
|
app:drawableEndCompat="@drawable/ic_sort" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
@ -286,6 +286,15 @@
|
|||||||
<item>@string/yt_shorts</item>
|
<item>@string/yt_shorts</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="playlistSortOptions">
|
||||||
|
<item>@string/least_recent</item>
|
||||||
|
<item>@string/most_recent</item>
|
||||||
|
<item>@string/duration</item>
|
||||||
|
<item>@string/duration_reversed</item>
|
||||||
|
<item>@string/playlist_name_az</item>
|
||||||
|
<item>@string/playlist_name_za</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="requiredNetwork">
|
<string-array name="requiredNetwork">
|
||||||
<item>@string/network_all</item>
|
<item>@string/network_all</item>
|
||||||
<item>@string/network_wifi</item>
|
<item>@string/network_wifi</item>
|
||||||
|
@ -408,6 +408,10 @@
|
|||||||
<string name="play_automatically_summary">Start playing video automatically when selecting</string>
|
<string name="play_automatically_summary">Start playing video automatically when selecting</string>
|
||||||
<string name="fullscreen_gestures">Enter/exit fullscreen gestures</string>
|
<string name="fullscreen_gestures">Enter/exit fullscreen gestures</string>
|
||||||
<string name="go_to_video">Go to video</string>
|
<string name="go_to_video">Go to video</string>
|
||||||
|
<string name="playlist_name_az">Playlist Name (A-Z)</string>
|
||||||
|
<string name="playlist_name_za">Playlist Name (Z-A)</string>
|
||||||
|
<string name="duration">Duration</string>
|
||||||
|
<string name="duration_reversed">Duration (reversed)</string>
|
||||||
|
|
||||||
<!-- Notification channel strings -->
|
<!-- Notification channel strings -->
|
||||||
<string name="download_channel_name">Download Service</string>
|
<string name="download_channel_name">Download Service</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user