Merge pull request #4415 from Bnyro/master

feat(queue): options to mark as (un)watched, and remove watched videos
This commit is contained in:
Bnyro 2023-08-09 13:06:31 +02:00 committed by GitHub
commit aa2f9aef4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 38 deletions

View File

@ -5,6 +5,9 @@ import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/**
* @param position: Position in milliseconds
*/
@Serializable @Serializable
@Entity(tableName = "watchPosition") @Entity(tableName = "watchPosition")
data class WatchPosition( data class WatchPosition(

View File

@ -17,7 +17,7 @@ import kotlinx.coroutines.withContext
/** /**
* Dialog with different options for a selected video. * Dialog with different options for a selected video.
* *
* Needs the [videoId] to load the content from the right video. * Needs the [channelId] to load the content from the right video.
*/ */
class ChannelOptionsBottomSheet( class ChannelOptionsBottomSheet(
private val channelId: String, private val channelId: String,

View File

@ -10,21 +10,30 @@ 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.databinding.QueueBottomSheetBinding import com.github.libretube.databinding.QueueBottomSheetBinding
import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.db.obj.WatchPosition
import com.github.libretube.extensions.toID
import com.github.libretube.ui.adapters.PlayingQueueAdapter import com.github.libretube.ui.adapters.PlayingQueueAdapter
import com.github.libretube.ui.dialogs.AddToPlaylistDialog import com.github.libretube.ui.dialogs.AddToPlaylistDialog
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
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlayingQueueSheet : ExpandedBottomSheet() { class PlayingQueueSheet : ExpandedBottomSheet() {
private lateinit var binding: QueueBottomSheetBinding private var _binding: QueueBottomSheetBinding? = null
private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = QueueBottomSheetBinding.inflate(layoutInflater) _binding = QueueBottomSheetBinding.inflate(layoutInflater)
return binding.root return binding.root
} }
@ -89,6 +98,10 @@ class PlayingQueueSheet : ExpandedBottomSheet() {
dialog?.dismiss() dialog?.dismiss()
} }
binding.watchPositionsOptions.setOnClickListener {
showWatchPositionsOptions()
}
val callback = object : ItemTouchHelper.SimpleCallback( val callback = object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT ItemTouchHelper.LEFT
@ -141,7 +154,60 @@ class PlayingQueueSheet : ExpandedBottomSheet() {
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
PlayingQueue.setStreams(newQueue) PlayingQueue.setStreams(newQueue)
binding.optionsRecycler.adapter?.notifyDataSetChanged() _binding?.optionsRecycler?.adapter?.notifyDataSetChanged()
}
.show()
}
@SuppressLint("NotifyDataSetChanged")
private fun showWatchPositionsOptions() {
val options = arrayOf(
getString(R.string.mark_as_watched),
getString(R.string.mark_as_unwatched),
getString(R.string.remove_watched_videos)
)
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.watch_positions)
.setItems(options) { _, index ->
when (index) {
0 -> {
CoroutineScope(Dispatchers.IO).launch {
PlayingQueue.getStreams().forEach {
val videoId = it.url.orEmpty().toID()
val duration = it.duration ?: 0
val watchPosition = WatchPosition(videoId, duration * 1000)
DatabaseHolder.Database.watchPositionDao().insert(watchPosition)
}
}
}
1 -> {
CoroutineScope(Dispatchers.IO).launch {
PlayingQueue.getStreams().forEach {
DatabaseHolder.Database.watchPositionDao()
.deleteByVideoId(it.url.orEmpty().toID())
}
}
}
2 -> {
CoroutineScope(Dispatchers.IO).launch {
val currentStream = PlayingQueue.getCurrent()
val streams = DatabaseHelper
.filterUnwatched(PlayingQueue.getStreams())
.toMutableList()
if (currentStream != null &&
streams.none { it.url?.toID() == currentStream.url?.toID() }
) {
streams.add(0, currentStream)
}
PlayingQueue.setStreams(streams)
withContext(Dispatchers.Main) {
_binding?.optionsRecycler?.adapter?.notifyDataSetChanged()
}
}
}
}
} }
.show() .show()
} }

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" />
</vector>

View File

@ -39,64 +39,42 @@
<ImageView <ImageView
android:id="@+id/repeat" android:id="@+id/repeat"
android:layout_width="wrap_content" style="@style/QueueSheetOption"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:src="@drawable/ic_repeat" /> android:src="@drawable/ic_repeat" />
<ImageView <ImageView
android:id="@+id/shuffle" android:id="@+id/shuffle"
android:layout_width="wrap_content" style="@style/QueueSheetOption"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:src="@drawable/ic_shuffle" /> android:src="@drawable/ic_shuffle" />
<ImageView <ImageView
android:id="@+id/reverse" android:id="@+id/reverse"
android:layout_width="wrap_content" style="@style/QueueSheetOption"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:src="@drawable/ic_reverse" /> android:src="@drawable/ic_reverse" />
<ImageView <ImageView
android:id="@+id/add_to_playlist" android:id="@+id/add_to_playlist"
android:layout_width="wrap_content" style="@style/QueueSheetOption"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:src="@drawable/ic_playlist_add" /> android:src="@drawable/ic_playlist_add" />
<ImageView <ImageView
android:id="@+id/sort" android:id="@+id/sort"
android:layout_width="wrap_content" style="@style/QueueSheetOption"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:src="@drawable/ic_sort" /> android:src="@drawable/ic_sort" />
<ImageView
android:id="@+id/watch_positions_options"
style="@style/QueueSheetOption"
android:src="@drawable/ic_eye" />
<ImageView <ImageView
android:id="@+id/clear_queue" android:id="@+id/clear_queue"
android:layout_width="wrap_content" style="@style/QueueSheetOption"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:src="@drawable/ic_close" /> android:src="@drawable/ic_close" />
<ImageView <ImageView
style="@style/QueueSheetOption"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="10dp"
android:paddingVertical="5dp"
android:layout_weight="1" android:layout_weight="1"
android:src="@drawable/ic_arrow_down" /> android:src="@drawable/ic_arrow_down" />

View File

@ -439,6 +439,7 @@
<string name="sort_by">Sort by</string> <string name="sort_by">Sort by</string>
<string name="uploader_name">Uploader name</string> <string name="uploader_name">Uploader name</string>
<string name="duration_span">Duration: %1$s</string> <string name="duration_span">Duration: %1$s</string>
<string name="remove_watched_videos">Remove watched videos</string>
<!-- Backup & Restore Settings --> <!-- Backup & Restore Settings -->
<string name="import_subscriptions_from">Import subscriptions from</string> <string name="import_subscriptions_from">Import subscriptions from</string>

View File

@ -248,4 +248,13 @@
</style> </style>
<style name="QueueSheetOption">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:background">?selectableItemBackgroundBorderless</item>
<item name="android:layout_marginStart">4dp</item>
<item name="android:layout_marginEnd">4dp</item>
<item name="android:padding">5dp</item>
</style>
</resources> </resources>