Merge pull request #1778 from Bnyro/master

Playlist page redesign
This commit is contained in:
Bnyro 2022-11-06 12:54:34 +01:00 committed by GitHub
commit ede95ea7d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 113 additions and 42 deletions

View File

@ -3,9 +3,8 @@ package com.github.libretube.extensions
/** /**
* format a Piped route to an ID * format a Piped route to an ID
*/ */
fun Any.toID(): String { fun String.toID(): String {
return this return this
.toString()
.replace("/watch?v=", "") // videos .replace("/watch?v=", "") // videos
.replace("/channel/", "") // channels .replace("/channel/", "") // channels
.replace("/playlist?list=", "") // playlists .replace("/playlist?list=", "") // playlists

View File

@ -1,7 +1,8 @@
package com.github.libretube.obj package com.github.libretube.obj
data class ShareData( data class ShareData(
val currentChannel: String ? = null, val currentChannel: String? = null,
val currentVideo: String ? = null, val currentVideo: String? = null,
val currentPlaylist: String ? = null val currentPlaylist: String? = null,
var currentPosition: Long? = null
) )

View File

@ -1,6 +1,7 @@
package com.github.libretube.ui.adapters package com.github.libretube.ui.adapters
import android.app.Activity import android.app.Activity
import android.content.Context
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -8,6 +9,7 @@ import android.view.ViewGroup
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.PlaylistId
import com.github.libretube.databinding.PlaylistRowBinding import com.github.libretube.databinding.PlaylistRowBinding
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.setFormattedDuration import com.github.libretube.extensions.setFormattedDuration
@ -28,7 +30,6 @@ class PlaylistAdapter(
private val videoFeed: MutableList<com.github.libretube.api.obj.StreamItem>, private val videoFeed: MutableList<com.github.libretube.api.obj.StreamItem>,
private val playlistId: String, private val playlistId: String,
private val isOwner: Boolean, private val isOwner: Boolean,
private val activity: Activity,
private val childFragmentManager: FragmentManager private val childFragmentManager: FragmentManager
) : RecyclerView.Adapter<PlaylistViewHolder>() { ) : RecyclerView.Adapter<PlaylistViewHolder>() {
@ -69,21 +70,24 @@ class PlaylistAdapter(
if (isOwner) { if (isOwner) {
deletePlaylist.visibility = View.VISIBLE deletePlaylist.visibility = View.VISIBLE
deletePlaylist.setOnClickListener { deletePlaylist.setOnClickListener {
removeFromPlaylist(position) removeFromPlaylist(root.context, position)
} }
} }
watchProgress.setWatchProgressLength(videoId, streamItem.duration!!) watchProgress.setWatchProgressLength(videoId, streamItem.duration!!)
} }
} }
fun removeFromPlaylist(position: Int) { fun removeFromPlaylist(context: Context, position: Int) {
videoFeed.removeAt(position) videoFeed.removeAt(position)
activity.runOnUiThread { notifyDataSetChanged() } (context as Activity).runOnUiThread {
notifyItemRemoved(position)
notifyItemRangeChanged(position, itemCount)
}
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
RetrofitInstance.authApi.removeFromPlaylist( RetrofitInstance.authApi.removeFromPlaylist(
PreferenceHelper.getToken(), PreferenceHelper.getToken(),
com.github.libretube.api.obj.PlaylistId( PlaylistId(
playlistId = playlistId, playlistId = playlistId,
index = position index = position
) )

View File

@ -20,8 +20,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
class ShareDialog( class ShareDialog(
private val id: String, private val id: String,
private val shareObjectType: ShareObjectType, private val shareObjectType: ShareObjectType,
private val shareData: ShareData, private val shareData: ShareData
private val position: Long? = null
) : DialogFragment() { ) : DialogFragment() {
private var binding: DialogShareBinding? = null private var binding: DialogShareBinding? = null
@ -35,7 +34,7 @@ class ShareDialog(
// add instanceUrl option if custom instance frontend url available // add instanceUrl option if custom instance frontend url available
if (instanceUrl != "") shareOptions += getString(R.string.instance) if (instanceUrl != "") shareOptions += getString(R.string.instance)
if (shareObjectType == ShareObjectType.VIDEO && position != null) { if (shareObjectType == ShareObjectType.VIDEO) {
setupTimeStampBinding() setupTimeStampBinding()
} }
@ -57,7 +56,7 @@ class ShareDialog(
} }
var url = "$host$path" var url = "$host$path"
if (shareObjectType == ShareObjectType.VIDEO && position != null && binding!!.timeCodeSwitch.isChecked) { if (shareObjectType == ShareObjectType.VIDEO && binding!!.timeCodeSwitch.isChecked) {
url += "&t=${binding!!.timeStamp.text}" url += "&t=${binding!!.timeStamp.text}"
} }
@ -85,7 +84,7 @@ class ShareDialog(
binding!!.timeCodeSwitch.setOnCheckedChangeListener { _, isChecked -> binding!!.timeCodeSwitch.setOnCheckedChangeListener { _, isChecked ->
binding!!.timeStampLayout.visibility = if (isChecked) View.VISIBLE else View.GONE binding!!.timeStampLayout.visibility = if (isChecked) View.VISIBLE else View.GONE
} }
binding!!.timeStamp.setText(position.toString()) binding!!.timeStamp.setText((shareData.currentPosition ?: 0L).toString())
if (binding!!.timeCodeSwitch.isChecked) binding!!.timeStampLayout.visibility = View.VISIBLE if (binding!!.timeCodeSwitch.isChecked) binding!!.timeStampLayout.visibility = View.VISIBLE
} }

View File

@ -363,12 +363,12 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
// share button // share button
binding.relPlayerShare.setOnClickListener { binding.relPlayerShare.setOnClickListener {
shareData.currentPosition = exoPlayer.currentPosition / 1000
val shareDialog = val shareDialog =
ShareDialog( ShareDialog(
videoId!!, videoId!!,
ShareObjectType.VIDEO, ShareObjectType.VIDEO,
shareData, shareData
exoPlayer.currentPosition / 1000
) )
shareDialog.show(childFragmentManager, ShareDialog::class.java.name) shareDialog.show(childFragmentManager, ShareDialog::class.java.name)
} }

View File

@ -1,5 +1,6 @@
package com.github.libretube.ui.fragments package com.github.libretube.ui.fragments
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -13,11 +14,16 @@ import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.FragmentPlaylistBinding import com.github.libretube.databinding.FragmentPlaylistBinding
import com.github.libretube.enums.ShareObjectType
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.obj.ShareData
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.base.BaseFragment
import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
import com.github.libretube.util.ImageHelper
import com.github.libretube.util.NavigationHelper
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
@ -58,6 +64,7 @@ class PlaylistFragment : BaseFragment() {
fetchPlaylist() fetchPlaylist()
} }
@SuppressLint("SetTextI18n")
private fun fetchPlaylist() { private fun fetchPlaylist() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
@ -79,27 +86,39 @@ class PlaylistFragment : BaseFragment() {
playlistName = response.name playlistName = response.name
isLoading = false isLoading = false
runOnUiThread { runOnUiThread {
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.uploader.text = response.uploader binding.playlistInfo.text = response.uploader + "" + getString(R.string.videoCount, response.videos.toString())
binding.videoCount.text =
getString(R.string.videoCount, response.videos.toString())
// show playlist options // show playlist options
binding.optionsMenu.setOnClickListener { binding.optionsMenu.setOnClickListener {
val optionsDialog = PlaylistOptionsBottomSheet(playlistId!!, playlistName!!, isOwner).show(
PlaylistOptionsBottomSheet(playlistId!!, playlistName!!, isOwner)
optionsDialog.show(
childFragmentManager, childFragmentManager,
PlaylistOptionsBottomSheet::class.java.name PlaylistOptionsBottomSheet::class.java.name
) )
} }
binding.playAll.setOnClickListener {
NavigationHelper.navigateVideo(
requireContext(),
response.relatedStreams?.first()?.url?.toID(),
playlistId
)
}
binding.share.setOnClickListener {
ShareDialog(
playlistId!!,
ShareObjectType.PLAYLIST,
ShareData(currentPlaylist = response.name)
).show(childFragmentManager, null)
}
playlistAdapter = PlaylistAdapter( playlistAdapter = PlaylistAdapter(
response.relatedStreams!!.toMutableList(), response.relatedStreams.orEmpty().toMutableList(),
playlistId!!, playlistId!!,
isOwner, isOwner,
requireActivity(),
childFragmentManager childFragmentManager
) )
@ -107,11 +126,11 @@ class PlaylistFragment : BaseFragment() {
playlistAdapter!!.registerAdapterDataObserver(object : playlistAdapter!!.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() { RecyclerView.AdapterDataObserver() {
override fun onChanged() { override fun onChanged() {
binding.videoCount.text = binding.playlistInfo.text =
getString( binding.playlistInfo.text.split("").first() + "" + getString(
R.string.videoCount, R.string.videoCount,
playlistAdapter!!.itemCount.toString() playlistAdapter!!.itemCount.toString()
) )
} }
}) })
@ -150,7 +169,7 @@ class PlaylistFragment : BaseFragment() {
direction: Int direction: Int
) { ) {
val position = viewHolder.absoluteAdapterPosition val position = viewHolder.absoluteAdapterPosition
playlistAdapter!!.removeFromPlaylist(position) playlistAdapter!!.removeFromPlaylist(requireContext(), position)
} }
} }

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M14,9L14,5l8,7 -8,7L14,15S2,14.069 2,19.737C2,8.4 14,9 14,9Z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000"/>
</vector>

View File

@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
android:paddingBottom="25dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -33,7 +34,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="25dp" android:layout_marginHorizontal="25dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_marginBottom="25dp"
android:hint="@string/time_code" android:hint="@string/time_code"
android:visibility="gone"> android:visibility="gone">

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -23,11 +24,23 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginVertical="10dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.ExtraLarge"
tools:src="@tools:sample/backgrounds/scenic" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="8dp"> android:paddingHorizontal="15dp"
android:layout_marginTop="8dp">
<TextView <TextView
android:id="@+id/playlist_name" android:id="@+id/playlist_name"
@ -48,17 +61,40 @@
</LinearLayout> </LinearLayout>
<TextView <TextView
android:id="@+id/uploader" android:id="@+id/playlistInfo"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp" android:paddingHorizontal="15dp"
android:paddingVertical="8dp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <LinearLayout
android:id="@+id/video_count"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp" /> android:layout_marginVertical="10dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/play_all"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_weight="1"
android:text="@string/play_all"
app:icon="@drawable/ic_playlist" />
<com.google.android.material.button.MaterialButton
android:id="@+id/share"
style="@style/Widget.Material3.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_weight="1"
android:text="@string/share"
app:icon="@drawable/ic_share_outlined" />
</LinearLayout>
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -359,6 +359,7 @@
<string name="confirm_unsubscribe">Are you sure you want to unsubscribe %1$s?</string> <string name="confirm_unsubscribe">Are you sure you want to unsubscribe %1$s?</string>
<string name="confirm_unsubscribing">Confirm unsubscribing</string> <string name="confirm_unsubscribing">Confirm unsubscribing</string>
<string name="confirm_unsubscribing_summary">Show a confirmation dialog before unsubscribing.</string> <string name="confirm_unsubscribing_summary">Show a confirmation dialog before unsubscribing.</string>
<string name="play_all">Play all</string>
<!-- Notification channel strings --> <!-- Notification channel strings -->
<string name="download_channel_name">Download Service</string> <string name="download_channel_name">Download Service</string>

View File

@ -195,12 +195,12 @@
</style> </style>
<style name="ElevatedIconButton" parent="@style/Widget.Material3.Button.IconButton.Filled.Tonal"> <style name="ElevatedIconButton" parent="@style/Widget.Material3.Button.IconButton">
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:paddingStart">15dp</item> <item name="android:paddingStart">10dp</item>
<item name="android:paddingEnd">15dp</item> <item name="android:paddingEnd">10dp</item>
<item name="android:stateListAnimator">@null</item> <item name="android:stateListAnimator">@null</item>
<item name="cardCornerRadius">25dp</item> <item name="cardCornerRadius">25dp</item>
<item name="android:drawableTint" tools:targetApi="m">?android:attr/textColorPrimary</item> <item name="android:drawableTint" tools:targetApi="m">?android:attr/textColorPrimary</item>