mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 22:30:30 +05:30
Merge pull request #2442 from Bnyro/master
Improve the playlist cloning algorithm
This commit is contained in:
commit
b98e6f5965
@ -6,6 +6,7 @@ import com.github.libretube.R
|
|||||||
import com.github.libretube.api.obj.Playlist
|
import com.github.libretube.api.obj.Playlist
|
||||||
import com.github.libretube.api.obj.PlaylistId
|
import com.github.libretube.api.obj.PlaylistId
|
||||||
import com.github.libretube.api.obj.Playlists
|
import com.github.libretube.api.obj.Playlists
|
||||||
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.YOUTUBE_FRONTEND_URL
|
import com.github.libretube.constants.YOUTUBE_FRONTEND_URL
|
||||||
import com.github.libretube.db.DatabaseHolder
|
import com.github.libretube.db.DatabaseHolder
|
||||||
import com.github.libretube.db.obj.LocalPlaylist
|
import com.github.libretube.db.obj.LocalPlaylist
|
||||||
@ -19,13 +20,13 @@ import com.github.libretube.extensions.toastFromMainThread
|
|||||||
import com.github.libretube.obj.ImportPlaylist
|
import com.github.libretube.obj.ImportPlaylist
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
import com.github.libretube.util.ProxyHelper
|
import com.github.libretube.util.ProxyHelper
|
||||||
|
import java.io.IOException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
object PlaylistsHelper {
|
object PlaylistsHelper {
|
||||||
private val pipedPlaylistRegex = "[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}".toRegex()
|
private val pipedPlaylistRegex = "[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}".toRegex()
|
||||||
@ -110,16 +111,17 @@ object PlaylistsHelper {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addToPlaylist(playlistId: String, vararg videoIds: String): Boolean {
|
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
.first { it.playlist.id.toString() == playlistId }
|
.first { it.playlist.id.toString() == playlistId }
|
||||||
|
|
||||||
for (videoId in videoIds) {
|
for (video in videos) {
|
||||||
val localPlaylistItem = RetrofitInstance.api.getStreams(videoId).toLocalPlaylistItem(playlistId, videoId)
|
val localPlaylistItem = video.toLocalPlaylistItem(playlistId)
|
||||||
awaitQuery {
|
awaitQuery {
|
||||||
// avoid duplicated videos in a playlist
|
// avoid duplicated videos in a playlist
|
||||||
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByVideoId(playlistId, videoId)
|
DatabaseHolder.Database.localPlaylistsDao()
|
||||||
|
.deletePlaylistItemsByVideoId(playlistId, localPlaylistItem.videoId)
|
||||||
|
|
||||||
// add the new video to the database
|
// add the new video to the database
|
||||||
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)
|
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)
|
||||||
@ -128,7 +130,9 @@ object PlaylistsHelper {
|
|||||||
// set the new playlist thumbnail URL
|
// set the new playlist thumbnail URL
|
||||||
localPlaylistItem.thumbnailUrl?.let {
|
localPlaylistItem.thumbnailUrl?.let {
|
||||||
localPlaylist.playlist.thumbnailUrl = it
|
localPlaylist.playlist.thumbnailUrl = it
|
||||||
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(localPlaylist.playlist)
|
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(
|
||||||
|
localPlaylist.playlist
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,7 +144,7 @@ object PlaylistsHelper {
|
|||||||
token,
|
token,
|
||||||
PlaylistId(
|
PlaylistId(
|
||||||
playlistId = playlistId,
|
playlistId = playlistId,
|
||||||
videoIds = videoIds.toList()
|
videoIds = videos.toList().map { it.url!!.toID() }
|
||||||
)
|
)
|
||||||
).message == "ok"
|
).message == "ok"
|
||||||
}
|
}
|
||||||
@ -172,13 +176,19 @@ object PlaylistsHelper {
|
|||||||
DatabaseHolder.Database.localPlaylistsDao().getAll()
|
DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
}.first { it.playlist.id.toString() == playlistId }
|
}.first { it.playlist.id.toString() == playlistId }
|
||||||
awaitQuery {
|
awaitQuery {
|
||||||
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(transaction.videos[index])
|
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(
|
||||||
|
transaction.videos[index]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (transaction.videos.size > 1) {
|
if (transaction.videos.size > 1) {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
transaction.videos[1].thumbnailUrl?.let { transaction.playlist.thumbnailUrl = it }
|
transaction.videos[1].thumbnailUrl?.let {
|
||||||
|
transaction.playlist.thumbnailUrl = it
|
||||||
|
}
|
||||||
awaitQuery {
|
awaitQuery {
|
||||||
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(transaction.playlist)
|
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(
|
||||||
|
transaction.playlist
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -203,12 +213,28 @@ object PlaylistsHelper {
|
|||||||
suspend fun importPlaylists(appContext: Context, playlists: List<ImportPlaylist>) {
|
suspend fun importPlaylists(appContext: Context, playlists: List<ImportPlaylist>) {
|
||||||
for (playlist in playlists) {
|
for (playlist in playlists) {
|
||||||
val playlistId = createPlaylist(playlist.name!!, appContext) ?: continue
|
val playlistId = createPlaylist(playlist.name!!, appContext) ?: continue
|
||||||
|
// if logged in, add the playlists by their ID via an api call
|
||||||
|
val success: Boolean = if (loggedIn) {
|
||||||
addToPlaylist(
|
addToPlaylist(
|
||||||
playlistId,
|
playlistId,
|
||||||
*playlist.videos.map {
|
*playlist.videos.map {
|
||||||
it.substring(it.length - 11, it.length)
|
StreamItem(url = it)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
// if not logged in, all video information needs to become fetched manually
|
||||||
|
try {
|
||||||
|
val streamItems = playlist.videos.map {
|
||||||
|
RetrofitInstance.api.getStreams(it).toStreamItem(it)
|
||||||
|
}
|
||||||
|
addToPlaylist(playlistId, *streamItems.toTypedArray())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appContext.toastFromMainThread(
|
||||||
|
if (success) R.string.importsuccess else R.string.server_error
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,9 +279,7 @@ object PlaylistsHelper {
|
|||||||
|
|
||||||
addToPlaylist(
|
addToPlaylist(
|
||||||
newPlaylist,
|
newPlaylist,
|
||||||
*playlist.relatedStreams.orEmpty()
|
*playlist.relatedStreams.orEmpty().toTypedArray()
|
||||||
.map { it.url!!.toID() }
|
|
||||||
.toTypedArray()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var nextPage = playlist.nextpage
|
var nextPage = playlist.nextpage
|
||||||
@ -264,9 +288,7 @@ object PlaylistsHelper {
|
|||||||
RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage).apply {
|
RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage).apply {
|
||||||
addToPlaylist(
|
addToPlaylist(
|
||||||
newPlaylist,
|
newPlaylist,
|
||||||
*relatedStreams.orEmpty()
|
*relatedStreams.orEmpty().toTypedArray()
|
||||||
.map { it.url!!.toID() }
|
|
||||||
.toTypedArray()
|
|
||||||
)
|
)
|
||||||
}.nextpage
|
}.nextpage
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -61,7 +61,12 @@ object SubscriptionHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleUnsubscribe(context: Context, channelId: String, channelName: String?, onUnsubscribe: () -> Unit) {
|
fun handleUnsubscribe(
|
||||||
|
context: Context,
|
||||||
|
channelId: String,
|
||||||
|
channelName: String?,
|
||||||
|
onUnsubscribe: () -> Unit
|
||||||
|
) {
|
||||||
if (!PreferenceHelper.getBoolean(PreferenceKeys.CONFIRM_UNSUBSCRIBE, false)) {
|
if (!PreferenceHelper.getBoolean(PreferenceKeys.CONFIRM_UNSUBSCRIBE, false)) {
|
||||||
unsubscribe(channelId)
|
unsubscribe(channelId)
|
||||||
onUnsubscribe.invoke()
|
onUnsubscribe.invoke()
|
||||||
|
@ -4,6 +4,8 @@ import kotlin.math.pow
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
fun Float.round(decimalPlaces: Int): Float {
|
fun Float.round(decimalPlaces: Int): Float {
|
||||||
return (this * 10.0.pow(decimalPlaces.toDouble())).roundToInt() / 10.0.pow(decimalPlaces.toDouble())
|
return (this * 10.0.pow(decimalPlaces.toDouble())).roundToInt() / 10.0.pow(
|
||||||
|
decimalPlaces.toDouble()
|
||||||
|
)
|
||||||
.toFloat()
|
.toFloat()
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
package com.github.libretube.extensions
|
package com.github.libretube.extensions
|
||||||
|
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.db.obj.LocalPlaylistItem
|
import com.github.libretube.db.obj.LocalPlaylistItem
|
||||||
|
|
||||||
fun Streams.toLocalPlaylistItem(playlistId: String, videoId: String): LocalPlaylistItem {
|
fun StreamItem.toLocalPlaylistItem(playlistId: String): LocalPlaylistItem {
|
||||||
return LocalPlaylistItem(
|
return LocalPlaylistItem(
|
||||||
playlistId = playlistId.toInt(),
|
playlistId = playlistId.toInt(),
|
||||||
videoId = videoId,
|
videoId = url!!.toID(),
|
||||||
title = title,
|
title = title,
|
||||||
thumbnailUrl = thumbnailUrl,
|
thumbnailUrl = thumbnail,
|
||||||
uploader = uploader,
|
uploader = uploaderName,
|
||||||
uploaderUrl = uploaderUrl,
|
uploaderUrl = uploaderUrl,
|
||||||
uploaderAvatar = uploaderAvatar,
|
uploaderAvatar = uploaderAvatar,
|
||||||
uploadDate = uploadDate,
|
uploadDate = uploadedDate,
|
||||||
duration = duration
|
duration = duration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,9 @@ class UpdateService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getDownloadDirectory(): File {
|
private fun getDownloadDirectory(): File {
|
||||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
val downloadsDir = Environment.getExternalStoragePublicDirectory(
|
||||||
|
Environment.DIRECTORY_DOWNLOADS
|
||||||
|
)
|
||||||
if (!downloadsDir.canWrite()) return DownloadHelper.getOfflineStorageDir(this)
|
if (!downloadsDir.canWrite()) return DownloadHelper.getOfflineStorageDir(this)
|
||||||
return downloadsDir
|
return downloadsDir
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,9 @@ class MainActivity : BaseActivity() {
|
|||||||
searchViewModel.setQuery(null)
|
searchViewModel.setQuery(null)
|
||||||
navController.navigate(R.id.searchFragment)
|
navController.navigate(R.id.searchFragment)
|
||||||
}
|
}
|
||||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS or MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW)
|
item.setShowAsAction(
|
||||||
|
MenuItem.SHOW_AS_ACTION_ALWAYS or MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
|
||||||
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,10 @@ class CommentsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
commentInfos.text = comment.author.toString() + TextUtils.SEPARATOR + comment.commentedTime.toString()
|
commentInfos.text = comment.author.toString() + TextUtils.SEPARATOR + comment.commentedTime.toString()
|
||||||
commentText.text = HtmlCompat.fromHtml(comment.commentText.toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)
|
commentText.text = HtmlCompat.fromHtml(
|
||||||
|
comment.commentText.toString(),
|
||||||
|
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||||
|
)
|
||||||
|
|
||||||
ImageHelper.loadImage(comment.thumbnail, commentorImage)
|
ImageHelper.loadImage(comment.thumbnail, commentorImage)
|
||||||
likesTextView.text = comment.likeCount.formatShort()
|
likesTextView.text = comment.likeCount.formatShort()
|
||||||
@ -102,7 +105,11 @@ class CommentsAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showMoreReplies(nextPage: String, showMoreBtn: Button, repliesAdapter: CommentsAdapter) {
|
private fun showMoreReplies(
|
||||||
|
nextPage: String,
|
||||||
|
showMoreBtn: Button,
|
||||||
|
repliesAdapter: CommentsAdapter
|
||||||
|
) {
|
||||||
when (repliesAdapter.itemCount) {
|
when (repliesAdapter.itemCount) {
|
||||||
0 -> {
|
0 -> {
|
||||||
fetchReplies(nextPage) {
|
fetchReplies(nextPage) {
|
||||||
|
@ -61,8 +61,14 @@ class DownloadsAdapter(
|
|||||||
) { _, index ->
|
) { _, index ->
|
||||||
when (index) {
|
when (index) {
|
||||||
0 -> {
|
0 -> {
|
||||||
val audioDir = DownloadHelper.getDownloadDir(root.context, DownloadHelper.AUDIO_DIR)
|
val audioDir = DownloadHelper.getDownloadDir(
|
||||||
val videoDir = DownloadHelper.getDownloadDir(root.context, DownloadHelper.VIDEO_DIR)
|
root.context,
|
||||||
|
DownloadHelper.AUDIO_DIR
|
||||||
|
)
|
||||||
|
val videoDir = DownloadHelper.getDownloadDir(
|
||||||
|
root.context,
|
||||||
|
DownloadHelper.VIDEO_DIR
|
||||||
|
)
|
||||||
|
|
||||||
listOf(audioDir, videoDir).forEach {
|
listOf(audioDir, videoDir).forEach {
|
||||||
val f = File(it, file.name)
|
val f = File(it, file.name)
|
||||||
|
@ -20,10 +20,10 @@ import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
|
|||||||
import com.github.libretube.ui.viewholders.PlaylistViewHolder
|
import com.github.libretube.ui.viewholders.PlaylistViewHolder
|
||||||
import com.github.libretube.util.ImageHelper
|
import com.github.libretube.util.ImageHelper
|
||||||
import com.github.libretube.util.NavigationHelper
|
import com.github.libretube.util.NavigationHelper
|
||||||
|
import java.io.IOException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class PlaylistAdapter(
|
class PlaylistAdapter(
|
||||||
private val videoFeed: MutableList<StreamItem>,
|
private val videoFeed: MutableList<StreamItem>,
|
||||||
|
@ -18,7 +18,11 @@ class PlaylistBookmarkAdapter(
|
|||||||
private val bookmarkMode: BookmarkMode = BookmarkMode.FRAGMENT
|
private val bookmarkMode: BookmarkMode = BookmarkMode.FRAGMENT
|
||||||
) : RecyclerView.Adapter<PlaylistBookmarkViewHolder>() {
|
) : RecyclerView.Adapter<PlaylistBookmarkViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistBookmarkViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistBookmarkViewHolder {
|
||||||
val binding = PlaylistBookmarkRowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = PlaylistBookmarkRowBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
return PlaylistBookmarkViewHolder(binding)
|
return PlaylistBookmarkViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +44,11 @@ class PlaylistBookmarkAdapter(
|
|||||||
uploaderName.text = bookmark.uploader
|
uploaderName.text = bookmark.uploader
|
||||||
|
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
NavigationHelper.navigatePlaylist(root.context, bookmark.playlistId, PlaylistType.PUBLIC)
|
NavigationHelper.navigatePlaylist(
|
||||||
|
root.context,
|
||||||
|
bookmark.playlistId,
|
||||||
|
PlaylistType.PUBLIC
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
root.setOnLongClickListener {
|
root.setOnLongClickListener {
|
||||||
|
@ -102,7 +102,10 @@ class SearchAdapter(
|
|||||||
val videoName = item.title!!
|
val videoName = item.title!!
|
||||||
root.setOnLongClickListener {
|
root.setOnLongClickListener {
|
||||||
VideoOptionsBottomSheet(videoId, videoName)
|
VideoOptionsBottomSheet(videoId, videoName)
|
||||||
.show((root.context as BaseActivity).supportFragmentManager, VideoOptionsBottomSheet::class.java.name)
|
.show(
|
||||||
|
(root.context as BaseActivity).supportFragmentManager,
|
||||||
|
VideoOptionsBottomSheet::class.java.name
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
channelContainer.setOnClickListener {
|
channelContainer.setOnClickListener {
|
||||||
@ -123,7 +126,10 @@ class SearchAdapter(
|
|||||||
searchViews.text = root.context.getString(
|
searchViews.text = root.context.getString(
|
||||||
R.string.subscribers,
|
R.string.subscribers,
|
||||||
item.subscribers.formatShort()
|
item.subscribers.formatShort()
|
||||||
) + TextUtils.SEPARATOR + root.context.getString(R.string.videoCount, item.videos.toString())
|
) + TextUtils.SEPARATOR + root.context.getString(
|
||||||
|
R.string.videoCount,
|
||||||
|
item.videos.toString()
|
||||||
|
)
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
NavigationHelper.navigateChannel(root.context, item.url)
|
NavigationHelper.navigateChannel(root.context, item.url)
|
||||||
}
|
}
|
||||||
@ -155,7 +161,10 @@ class SearchAdapter(
|
|||||||
val playlistId = item.url!!.toID()
|
val playlistId = item.url!!.toID()
|
||||||
val playlistName = item.name!!
|
val playlistName = item.name!!
|
||||||
PlaylistOptionsBottomSheet(playlistId, playlistName, PlaylistType.PUBLIC)
|
PlaylistOptionsBottomSheet(playlistId, playlistName, PlaylistType.PUBLIC)
|
||||||
.show((root.context as BaseActivity).supportFragmentManager, PlaylistOptionsBottomSheet::class.java.name)
|
.show(
|
||||||
|
(root.context as BaseActivity).supportFragmentManager,
|
||||||
|
PlaylistOptionsBottomSheet::class.java.name
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,15 @@ class VideosAdapter(
|
|||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder {
|
||||||
val layoutInflater = LayoutInflater.from(parent.context)
|
val layoutInflater = LayoutInflater.from(parent.context)
|
||||||
return when {
|
return when {
|
||||||
viewType == CAUGHT_UP_TYPE -> VideosViewHolder(AllCaughtUpRowBinding.inflate(layoutInflater, parent, false))
|
viewType == CAUGHT_UP_TYPE -> VideosViewHolder(
|
||||||
forceMode in listOf(ForceMode.TRENDING, ForceMode.RELATED, ForceMode.HOME) -> VideosViewHolder(TrendingRowBinding.inflate(layoutInflater, parent, false))
|
AllCaughtUpRowBinding.inflate(layoutInflater, parent, false)
|
||||||
forceMode == ForceMode.CHANNEL -> VideosViewHolder(VideoRowBinding.inflate(layoutInflater, parent, false))
|
)
|
||||||
|
forceMode in listOf(ForceMode.TRENDING, ForceMode.RELATED, ForceMode.HOME) -> VideosViewHolder(
|
||||||
|
TrendingRowBinding.inflate(layoutInflater, parent, false)
|
||||||
|
)
|
||||||
|
forceMode == ForceMode.CHANNEL -> VideosViewHolder(
|
||||||
|
VideoRowBinding.inflate(layoutInflater, parent, false)
|
||||||
|
)
|
||||||
PreferenceHelper.getBoolean(
|
PreferenceHelper.getBoolean(
|
||||||
PreferenceKeys.ALTERNATIVE_VIDEOS_LAYOUT,
|
PreferenceKeys.ALTERNATIVE_VIDEOS_LAYOUT,
|
||||||
false
|
false
|
||||||
@ -102,7 +108,9 @@ class VideosAdapter(
|
|||||||
video.uploaderName + TextUtils.SEPARATOR +
|
video.uploaderName + TextUtils.SEPARATOR +
|
||||||
video.views.formatShort() + " " +
|
video.views.formatShort() + " " +
|
||||||
root.context.getString(R.string.views_placeholder) +
|
root.context.getString(R.string.views_placeholder) +
|
||||||
TextUtils.SEPARATOR + video.uploaded?.let { DateUtils.getRelativeTimeSpanString(it) }
|
TextUtils.SEPARATOR + video.uploaded?.let {
|
||||||
|
DateUtils.getRelativeTimeSpanString(it)
|
||||||
|
}
|
||||||
video.duration?.let { thumbnailDuration.setFormattedDuration(it) }
|
video.duration?.let { thumbnailDuration.setFormattedDuration(it) }
|
||||||
channelImage.setOnClickListener {
|
channelImage.setOnClickListener {
|
||||||
NavigationHelper.navigateChannel(root.context, video.uploaderUrl)
|
NavigationHelper.navigateChannel(root.context, video.uploaderUrl)
|
||||||
@ -118,7 +126,10 @@ class VideosAdapter(
|
|||||||
if (videoId == null || videoName == null) return@setOnLongClickListener true
|
if (videoId == null || videoName == null) return@setOnLongClickListener true
|
||||||
|
|
||||||
VideoOptionsBottomSheet(videoId, videoName)
|
VideoOptionsBottomSheet(videoId, videoName)
|
||||||
.show((root.context as BaseActivity).supportFragmentManager, VideoOptionsBottomSheet::class.java.name)
|
.show(
|
||||||
|
(root.context as BaseActivity).supportFragmentManager,
|
||||||
|
VideoOptionsBottomSheet::class.java.name
|
||||||
|
)
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -134,7 +145,9 @@ class VideosAdapter(
|
|||||||
videoInfo.text =
|
videoInfo.text =
|
||||||
video.views.formatShort() + " " +
|
video.views.formatShort() + " " +
|
||||||
root.context.getString(R.string.views_placeholder) +
|
root.context.getString(R.string.views_placeholder) +
|
||||||
TextUtils.SEPARATOR + video.uploaded?.let { DateUtils.getRelativeTimeSpanString(it) }
|
TextUtils.SEPARATOR + video.uploaded?.let {
|
||||||
|
DateUtils.getRelativeTimeSpanString(it)
|
||||||
|
}
|
||||||
|
|
||||||
thumbnailDuration.text =
|
thumbnailDuration.text =
|
||||||
video.duration?.let { DateUtils.formatElapsedTime(it) }
|
video.duration?.let { DateUtils.formatElapsedTime(it) }
|
||||||
@ -159,7 +172,10 @@ class VideosAdapter(
|
|||||||
root.setOnLongClickListener {
|
root.setOnLongClickListener {
|
||||||
if (videoId == null || videoName == null) return@setOnLongClickListener true
|
if (videoId == null || videoName == null) return@setOnLongClickListener true
|
||||||
VideoOptionsBottomSheet(videoId, videoName)
|
VideoOptionsBottomSheet(videoId, videoName)
|
||||||
.show((root.context as BaseActivity).supportFragmentManager, VideoOptionsBottomSheet::class.java.name)
|
.show(
|
||||||
|
(root.context as BaseActivity).supportFragmentManager,
|
||||||
|
VideoOptionsBottomSheet::class.java.name
|
||||||
|
)
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,10 @@ class WatchHistoryAdapter(
|
|||||||
}
|
}
|
||||||
root.setOnLongClickListener {
|
root.setOnLongClickListener {
|
||||||
VideoOptionsBottomSheet(video.videoId, video.title!!)
|
VideoOptionsBottomSheet(video.videoId, video.title!!)
|
||||||
.show((root.context as BaseActivity).supportFragmentManager, VideoOptionsBottomSheet::class.java.name)
|
.show(
|
||||||
|
(root.context as BaseActivity).supportFragmentManager,
|
||||||
|
VideoOptionsBottomSheet::class.java.name
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,10 @@ import androidx.fragment.app.activityViewModels
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.PlaylistsHelper
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.databinding.DialogAddtoplaylistBinding
|
import com.github.libretube.databinding.DialogAddtoplaylistBinding
|
||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
|
import com.github.libretube.extensions.toStreamItem
|
||||||
import com.github.libretube.extensions.toastFromMainThread
|
import com.github.libretube.extensions.toastFromMainThread
|
||||||
import com.github.libretube.ui.models.PlaylistViewModel
|
import com.github.libretube.ui.models.PlaylistViewModel
|
||||||
import com.github.libretube.util.ThemeHelper
|
import com.github.libretube.util.ThemeHelper
|
||||||
@ -86,7 +88,10 @@ class AddToPlaylistDialog(
|
|||||||
val appContext = context?.applicationContext ?: return
|
val appContext = context?.applicationContext ?: return
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val success = try {
|
val success = try {
|
||||||
PlaylistsHelper.addToPlaylist(playlistId, videoId)
|
PlaylistsHelper.addToPlaylist(
|
||||||
|
playlistId,
|
||||||
|
RetrofitInstance.api.getStreams(videoId).toStreamItem(videoId)
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG(), e.toString())
|
Log.e(TAG(), e.toString())
|
||||||
appContext.toastFromMainThread(R.string.unknown_error)
|
appContext.toastFromMainThread(R.string.unknown_error)
|
||||||
|
@ -38,7 +38,9 @@ class DeletePlaylistDialog(
|
|||||||
if (playlistType == PlaylistType.LOCAL) {
|
if (playlistType == PlaylistType.LOCAL) {
|
||||||
awaitQuery {
|
awaitQuery {
|
||||||
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistId)
|
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistId)
|
||||||
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(playlistId)
|
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(
|
||||||
|
playlistId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onSuccess.invoke()
|
onSuccess.invoke()
|
||||||
return
|
return
|
||||||
|
@ -21,8 +21,8 @@ import com.github.libretube.util.ImageHelper
|
|||||||
import com.github.libretube.util.MetadataHelper
|
import com.github.libretube.util.MetadataHelper
|
||||||
import com.github.libretube.util.ThemeHelper
|
import com.github.libretube.util.ThemeHelper
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import retrofit2.HttpException
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import retrofit2.HttpException
|
||||||
|
|
||||||
class DownloadDialog(
|
class DownloadDialog(
|
||||||
private val videoId: String
|
private val videoId: String
|
||||||
|
@ -20,7 +20,10 @@ class NavBarOptionsDialog : DialogFragment() {
|
|||||||
|
|
||||||
val options = NavBarHelper.getNavBarItems(requireContext())
|
val options = NavBarHelper.getNavBarItems(requireContext())
|
||||||
|
|
||||||
val adapter = NavBarOptionsAdapter(options.toMutableList(), NavBarHelper.getStartFragmentId(requireContext()))
|
val adapter = NavBarOptionsAdapter(
|
||||||
|
options.toMutableList(),
|
||||||
|
NavBarHelper.getStartFragmentId(requireContext())
|
||||||
|
)
|
||||||
|
|
||||||
val itemTouchCallback = object : ItemTouchHelper.Callback() {
|
val itemTouchCallback = object : ItemTouchHelper.Callback() {
|
||||||
override fun getMovementFlags(
|
override fun getMovementFlags(
|
||||||
|
@ -27,11 +27,11 @@ 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 com.github.libretube.util.ImageHelper
|
import com.github.libretube.util.ImageHelper
|
||||||
|
import java.io.IOException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class ChannelFragment : BaseFragment() {
|
class ChannelFragment : BaseFragment() {
|
||||||
private lateinit var binding: FragmentChannelBinding
|
private lateinit var binding: FragmentChannelBinding
|
||||||
@ -134,7 +134,11 @@ class ChannelFragment : BaseFragment() {
|
|||||||
if (isSubscribed == null) return@launchWhenCreated
|
if (isSubscribed == null) return@launchWhenCreated
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
binding.channelSubscribe.setupSubscriptionButton(channelId, channelName, binding.notificationBell)
|
binding.channelSubscribe.setupSubscriptionButton(
|
||||||
|
channelId,
|
||||||
|
channelName,
|
||||||
|
binding.notificationBell
|
||||||
|
)
|
||||||
|
|
||||||
binding.channelShare.setOnClickListener {
|
binding.channelShare.setOnClickListener {
|
||||||
val shareDialog = ShareDialog(
|
val shareDialog = ShareDialog(
|
||||||
|
@ -79,7 +79,11 @@ class HomeFragment : BaseFragment() {
|
|||||||
if (feed.isEmpty()) return@runOrError
|
if (feed.isEmpty()) return@runOrError
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
makeVisible(binding.featuredRV, binding.featuredTV)
|
makeVisible(binding.featuredRV, binding.featuredTV)
|
||||||
binding.featuredRV.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
binding.featuredRV.layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
binding.featuredRV.adapter = VideosAdapter(
|
binding.featuredRV.adapter = VideosAdapter(
|
||||||
feed.toMutableList(),
|
feed.toMutableList(),
|
||||||
forceMode = VideosAdapter.Companion.ForceMode.HOME
|
forceMode = VideosAdapter.Companion.ForceMode.HOME
|
||||||
@ -108,7 +112,10 @@ class HomeFragment : BaseFragment() {
|
|||||||
runOnUiThread {
|
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(playlists.toMutableList(), PlaylistsHelper.getPrivatePlaylistType())
|
binding.playlistsRV.adapter = PlaylistsAdapter(
|
||||||
|
playlists.toMutableList(),
|
||||||
|
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) {
|
||||||
@ -129,7 +136,11 @@ class HomeFragment : BaseFragment() {
|
|||||||
if (bookmarkedPlaylists.isEmpty()) return@runOrError
|
if (bookmarkedPlaylists.isEmpty()) return@runOrError
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
makeVisible(binding.bookmarksTV, binding.bookmarksRV)
|
makeVisible(binding.bookmarksTV, binding.bookmarksRV)
|
||||||
binding.bookmarksRV.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
binding.bookmarksRV.layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
binding.bookmarksRV.adapter = PlaylistBookmarkAdapter(
|
binding.bookmarksRV.adapter = PlaylistBookmarkAdapter(
|
||||||
bookmarkedPlaylists,
|
bookmarkedPlaylists,
|
||||||
PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME
|
PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME
|
||||||
|
@ -105,15 +105,15 @@ import com.google.android.exoplayer2.ui.StyledPlayerView
|
|||||||
import com.google.android.exoplayer2.upstream.DefaultDataSource
|
import com.google.android.exoplayer2.upstream.DefaultDataSource
|
||||||
import com.google.android.exoplayer2.util.MimeTypes
|
import com.google.android.exoplayer2.util.MimeTypes
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.chromium.net.CronetEngine
|
import org.chromium.net.CronetEngine
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
||||||
|
|
||||||
@ -1160,7 +1160,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
if (resolutions.isEmpty()) {
|
if (resolutions.isEmpty()) {
|
||||||
return listOf(
|
return listOf(
|
||||||
VideoResolution(getString(R.string.hls), resolution = Int.MAX_VALUE, adaptiveSourceUrl = streams.hls)
|
VideoResolution(
|
||||||
|
getString(R.string.hls),
|
||||||
|
resolution = Int.MAX_VALUE,
|
||||||
|
adaptiveSourceUrl = streams.hls
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
resolutions.add(0, VideoResolution(getString(R.string.auto_quality), Int.MAX_VALUE))
|
resolutions.add(0, VideoResolution(getString(R.string.auto_quality), Int.MAX_VALUE))
|
||||||
@ -1187,7 +1191,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
// set the default subtitle if available
|
// set the default subtitle if available
|
||||||
val newParams = trackSelector.buildUponParameters()
|
val newParams = trackSelector.buildUponParameters()
|
||||||
if (PlayerHelper.defaultSubtitleCode != "" && subtitleCodesList.contains(PlayerHelper.defaultSubtitleCode)) {
|
if (PlayerHelper.defaultSubtitleCode != "" && subtitleCodesList.contains(
|
||||||
|
PlayerHelper.defaultSubtitleCode
|
||||||
|
)
|
||||||
|
) {
|
||||||
newParams
|
newParams
|
||||||
.setPreferredTextLanguage(PlayerHelper.defaultSubtitleCode)
|
.setPreferredTextLanguage(PlayerHelper.defaultSubtitleCode)
|
||||||
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
|
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
|
||||||
@ -1464,7 +1471,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val backgroundModeRunning = BackgroundHelper.isServiceRunning(requireContext(), BackgroundMode::class.java)
|
val backgroundModeRunning = BackgroundHelper.isServiceRunning(
|
||||||
|
requireContext(),
|
||||||
|
BackgroundMode::class.java
|
||||||
|
)
|
||||||
|
|
||||||
return exoPlayer.isPlaying && !backgroundModeRunning
|
return exoPlayer.isPlaying && !backgroundModeRunning
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,8 @@ import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
|
|||||||
import com.github.libretube.util.ImageHelper
|
import com.github.libretube.util.ImageHelper
|
||||||
import com.github.libretube.util.NavigationHelper
|
import com.github.libretube.util.NavigationHelper
|
||||||
import com.github.libretube.util.TextUtils
|
import com.github.libretube.util.TextUtils
|
||||||
import retrofit2.HttpException
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import retrofit2.HttpException
|
||||||
|
|
||||||
class PlaylistFragment : BaseFragment() {
|
class PlaylistFragment : BaseFragment() {
|
||||||
private lateinit var binding: FragmentPlaylistBinding
|
private lateinit var binding: FragmentPlaylistBinding
|
||||||
|
@ -18,8 +18,8 @@ import com.github.libretube.extensions.hideKeyboard
|
|||||||
import com.github.libretube.ui.adapters.SearchAdapter
|
import com.github.libretube.ui.adapters.SearchAdapter
|
||||||
import com.github.libretube.ui.base.BaseFragment
|
import com.github.libretube.ui.base.BaseFragment
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
import retrofit2.HttpException
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import retrofit2.HttpException
|
||||||
|
|
||||||
class SearchResultFragment : BaseFragment() {
|
class SearchResultFragment : BaseFragment() {
|
||||||
private lateinit var binding: FragmentSearchResultBinding
|
private lateinit var binding: FragmentSearchResultBinding
|
||||||
|
@ -150,7 +150,9 @@ class SubscriptionsFragment : BaseFragment() {
|
|||||||
// add an "all caught up item"
|
// add an "all caught up item"
|
||||||
if (sortOrder == 0) {
|
if (sortOrder == 0) {
|
||||||
val lastCheckedFeedTime = PreferenceHelper.getLastCheckedFeedTime()
|
val lastCheckedFeedTime = PreferenceHelper.getLastCheckedFeedTime()
|
||||||
val caughtUpIndex = feed.indexOfFirst { (it.uploaded ?: 0L) / 1000 < lastCheckedFeedTime }
|
val caughtUpIndex = feed.indexOfFirst {
|
||||||
|
(it.uploaded ?: 0L) / 1000 < lastCheckedFeedTime
|
||||||
|
}
|
||||||
if (caughtUpIndex > 0) {
|
if (caughtUpIndex > 0) {
|
||||||
sortedFeed.add(caughtUpIndex, StreamItem(type = "caught"))
|
sortedFeed.add(caughtUpIndex, StreamItem(type = "caught"))
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ import com.github.libretube.ui.adapters.VideosAdapter
|
|||||||
import com.github.libretube.ui.base.BaseFragment
|
import com.github.libretube.ui.base.BaseFragment
|
||||||
import com.github.libretube.util.LocaleHelper
|
import com.github.libretube.util.LocaleHelper
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import retrofit2.HttpException
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import retrofit2.HttpException
|
||||||
|
|
||||||
class TrendsFragment : BaseFragment() {
|
class TrendsFragment : BaseFragment() {
|
||||||
private lateinit var binding: FragmentTrendsBinding
|
private lateinit var binding: FragmentTrendsBinding
|
||||||
|
@ -42,7 +42,10 @@ class AppearanceSettings : BasePreferenceFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val changeIcon = findPreference<Preference>(PreferenceKeys.APP_ICON)
|
val changeIcon = findPreference<Preference>(PreferenceKeys.APP_ICON)
|
||||||
val iconPref = PreferenceHelper.getString(PreferenceKeys.APP_ICON, IconsSheetAdapter.Companion.AppIcon.Default.activityAlias)
|
val iconPref = PreferenceHelper.getString(
|
||||||
|
PreferenceKeys.APP_ICON,
|
||||||
|
IconsSheetAdapter.Companion.AppIcon.Default.activityAlias
|
||||||
|
)
|
||||||
IconsSheetAdapter.availableIcons.firstOrNull { it.activityAlias == iconPref }?.let {
|
IconsSheetAdapter.availableIcons.firstOrNull { it.activityAlias == iconPref }?.let {
|
||||||
changeIcon?.summary = getString(it.nameResource)
|
changeIcon?.summary = getString(it.nameResource)
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,11 @@ class MainSettings : BasePreferenceFragment() {
|
|||||||
// checking for update: yes -> dialog, no -> snackBar
|
// checking for update: yes -> dialog, no -> snackBar
|
||||||
update?.setOnPreferenceClickListener {
|
update?.setOnPreferenceClickListener {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Toast.makeText(context, "Updater is disabled for debug versions!", Toast.LENGTH_SHORT).show()
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Updater is disabled for debug versions!",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
@ -40,9 +40,15 @@ class NotificationSettings : BasePreferenceFragment() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
val notificationTime = findPreference<SwitchPreferenceCompat>(PreferenceKeys.NOTIFICATION_TIME_ENABLED)
|
val notificationTime = findPreference<SwitchPreferenceCompat>(
|
||||||
val notificationStartTime = findPreference<TimePickerPreference>(PreferenceKeys.NOTIFICATION_START_TIME)
|
PreferenceKeys.NOTIFICATION_TIME_ENABLED
|
||||||
val notificationEndTime = findPreference<TimePickerPreference>(PreferenceKeys.NOTIFICATION_END_TIME)
|
)
|
||||||
|
val notificationStartTime = findPreference<TimePickerPreference>(
|
||||||
|
PreferenceKeys.NOTIFICATION_START_TIME
|
||||||
|
)
|
||||||
|
val notificationEndTime = findPreference<TimePickerPreference>(
|
||||||
|
PreferenceKeys.NOTIFICATION_END_TIME
|
||||||
|
)
|
||||||
listOf(notificationStartTime, notificationEndTime).forEach {
|
listOf(notificationStartTime, notificationEndTime).forEach {
|
||||||
it?.isEnabled = notificationTime?.isChecked == true
|
it?.isEnabled = notificationTime?.isChecked == true
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,11 @@ class CommentsSheet(
|
|||||||
private lateinit var commentsAdapter: CommentsAdapter
|
private lateinit var commentsAdapter: CommentsAdapter
|
||||||
private var isLoading = false
|
private var isLoading = false
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
binding = CommentsSheetBinding.inflate(layoutInflater)
|
binding = CommentsSheetBinding.inflate(layoutInflater)
|
||||||
// set a fixed maximum height
|
// set a fixed maximum height
|
||||||
binding.root.maxHeight = maxHeight
|
binding.root.maxHeight = maxHeight
|
||||||
|
@ -244,8 +244,12 @@ internal class CustomExoPlayerView(
|
|||||||
R.drawable.ic_aspect_ratio,
|
R.drawable.ic_aspect_ratio,
|
||||||
{
|
{
|
||||||
when (resizeMode) {
|
when (resizeMode) {
|
||||||
AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(R.string.resize_mode_fit)
|
AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(
|
||||||
AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(R.string.resize_mode_fill)
|
R.string.resize_mode_fit
|
||||||
|
)
|
||||||
|
AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(
|
||||||
|
R.string.resize_mode_fill
|
||||||
|
)
|
||||||
else -> context.getString(R.string.resize_mode_zoom)
|
else -> context.getString(R.string.resize_mode_zoom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,7 +407,9 @@ internal class CustomExoPlayerView(
|
|||||||
// If brightness progress goes to below 0, set to system brightness
|
// If brightness progress goes to below 0, set to system brightness
|
||||||
if (distance <= 0) {
|
if (distance <= 0) {
|
||||||
brightnessHelper.resetToSystemBrightness()
|
brightnessHelper.resetToSystemBrightness()
|
||||||
gestureViewBinding.brightnessImageView.setImageResource(R.drawable.ic_brightness_auto)
|
gestureViewBinding.brightnessImageView.setImageResource(
|
||||||
|
R.drawable.ic_brightness_auto
|
||||||
|
)
|
||||||
gestureViewBinding.brightnessTextView.text = resources.getString(R.string.auto)
|
gestureViewBinding.brightnessTextView.text = resources.getString(R.string.auto)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,10 @@ class SliderPreference(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.SliderPreference)
|
private val typedArray = context.obtainStyledAttributes(
|
||||||
|
attributeSet,
|
||||||
|
R.styleable.SliderPreference
|
||||||
|
)
|
||||||
|
|
||||||
override fun onAttached() {
|
override fun onAttached() {
|
||||||
super.onAttached()
|
super.onAttached()
|
||||||
|
@ -52,7 +52,12 @@ class BrightnessHelper(private val activity: Activity) {
|
|||||||
* Set current brightness value with scaling to given range.
|
* Set current brightness value with scaling to given range.
|
||||||
* [shouldSave] determines whether the value should be persisted.
|
* [shouldSave] determines whether the value should be persisted.
|
||||||
*/
|
*/
|
||||||
fun setBrightnessWithScale(value: Float, maxValue: Float, minValue: Float = 0.0f, shouldSave: Boolean = false) {
|
fun setBrightnessWithScale(
|
||||||
|
value: Float,
|
||||||
|
maxValue: Float,
|
||||||
|
minValue: Float = 0.0f,
|
||||||
|
shouldSave: Boolean = false
|
||||||
|
) {
|
||||||
brightness = value.normalize(minValue, maxValue, minBrightness, maxBrightness)
|
brightness = value.normalize(minValue, maxValue, minBrightness, maxBrightness)
|
||||||
if (shouldSave) savedBrightness = brightness
|
if (shouldSave) savedBrightness = brightness
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,14 @@ package com.github.libretube.util
|
|||||||
|
|
||||||
import com.github.libretube.api.obj.PipedStream
|
import com.github.libretube.api.obj.PipedStream
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
import org.w3c.dom.Document
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
import javax.xml.parsers.DocumentBuilder
|
import javax.xml.parsers.DocumentBuilder
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
import javax.xml.transform.TransformerFactory
|
import javax.xml.transform.TransformerFactory
|
||||||
import javax.xml.transform.dom.DOMSource
|
import javax.xml.transform.dom.DOMSource
|
||||||
import javax.xml.transform.stream.StreamResult
|
import javax.xml.transform.stream.StreamResult
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
// Based off of https://github.com/TeamPiped/Piped/blob/master/src/utils/DashUtils.js
|
// Based off of https://github.com/TeamPiped/Piped/blob/master/src/utils/DashUtils.js
|
||||||
|
|
||||||
@ -66,7 +66,9 @@ object DashHelper {
|
|||||||
|
|
||||||
for (stream in streams.audioStreams!!) {
|
for (stream in streams.audioStreams!!) {
|
||||||
val adapSetInfo =
|
val adapSetInfo =
|
||||||
adapSetInfos.find { it.mimeType == stream.mimeType && it.audioTrackId == stream.audioTrackId }
|
adapSetInfos.find {
|
||||||
|
it.mimeType == stream.mimeType && it.audioTrackId == stream.audioTrackId
|
||||||
|
}
|
||||||
if (adapSetInfo != null) {
|
if (adapSetInfo != null) {
|
||||||
adapSetInfo.formats.add(stream)
|
adapSetInfo.formats.add(stream)
|
||||||
continue
|
continue
|
||||||
|
@ -13,9 +13,9 @@ import coil.request.CachePolicy
|
|||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.github.libretube.api.CronetHelper
|
import com.github.libretube.api.CronetHelper
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import okio.use
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import okio.use
|
||||||
|
|
||||||
object ImageHelper {
|
object ImageHelper {
|
||||||
lateinit var imageLoader: ImageLoader
|
lateinit var imageLoader: ImageLoader
|
||||||
|
@ -15,11 +15,11 @@ import com.github.libretube.obj.ImportPlaylist
|
|||||||
import com.github.libretube.obj.ImportPlaylistFile
|
import com.github.libretube.obj.ImportPlaylistFile
|
||||||
import com.github.libretube.obj.NewPipeSubscription
|
import com.github.libretube.obj.NewPipeSubscription
|
||||||
import com.github.libretube.obj.NewPipeSubscriptions
|
import com.github.libretube.obj.NewPipeSubscriptions
|
||||||
|
import java.io.FileOutputStream
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
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 java.io.FileOutputStream
|
|
||||||
|
|
||||||
class ImportHelper(
|
class ImportHelper(
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
@ -56,7 +56,10 @@ class ImportHelper(
|
|||||||
return when (val fileType = activity.contentResolver.getType(uri)) {
|
return when (val fileType = activity.contentResolver.getType(uri)) {
|
||||||
"application/json", "application/*", "application/octet-stream" -> {
|
"application/json", "application/*", "application/octet-stream" -> {
|
||||||
// NewPipe subscriptions format
|
// NewPipe subscriptions format
|
||||||
val subscriptions = ObjectMapper().readValue(uri.readText(), NewPipeSubscriptions::class.java)
|
val subscriptions = ObjectMapper().readValue(
|
||||||
|
uri.readText(),
|
||||||
|
NewPipeSubscriptions::class.java
|
||||||
|
)
|
||||||
subscriptions.subscriptions.orEmpty().map {
|
subscriptions.subscriptions.orEmpty().map {
|
||||||
it.url!!.replace("https://www.youtube.com/channel/", "")
|
it.url!!.replace("https://www.youtube.com/channel/", "")
|
||||||
}
|
}
|
||||||
@ -131,7 +134,10 @@ class ImportHelper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"application/json", "application/*", "application/octet-stream" -> {
|
"application/json", "application/*", "application/octet-stream" -> {
|
||||||
val playlistFile = ObjectMapper().readValue(uri.readText(), ImportPlaylistFile::class.java)
|
val playlistFile = ObjectMapper().readValue(
|
||||||
|
uri.readText(),
|
||||||
|
ImportPlaylistFile::class.java
|
||||||
|
)
|
||||||
importPlaylists.addAll(playlistFile.playlists.orEmpty())
|
importPlaylists.addAll(playlistFile.playlists.orEmpty())
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -124,7 +124,13 @@ object PreferenceHelper {
|
|||||||
|
|
||||||
fun toggleIgnorableNotificationChannel(channelId: String) {
|
fun toggleIgnorableNotificationChannel(channelId: String) {
|
||||||
val ignorableChannels = getIgnorableNotificationChannels().toMutableList()
|
val ignorableChannels = getIgnorableNotificationChannels().toMutableList()
|
||||||
if (ignorableChannels.contains(channelId)) ignorableChannels.remove(channelId) else ignorableChannels.add(channelId)
|
if (ignorableChannels.contains(channelId)) {
|
||||||
|
ignorableChannels.remove(channelId)
|
||||||
|
} else {
|
||||||
|
ignorableChannels.add(
|
||||||
|
channelId
|
||||||
|
)
|
||||||
|
}
|
||||||
editor.putString(
|
editor.putString(
|
||||||
PreferenceKeys.IGNORED_NOTIFICATION_CHANNELS,
|
PreferenceKeys.IGNORED_NOTIFICATION_CHANNELS,
|
||||||
ignorableChannels.joinToString(",")
|
ignorableChannels.joinToString(",")
|
||||||
|
@ -2,13 +2,13 @@ package com.github.libretube.util
|
|||||||
|
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
object ProxyHelper {
|
object ProxyHelper {
|
||||||
private fun getImageProxyUrl(): String? {
|
private fun getImageProxyUrl(): String? {
|
||||||
|
@ -17,8 +17,8 @@ import com.github.libretube.extensions.toID
|
|||||||
import com.github.libretube.ui.activities.MainActivity
|
import com.github.libretube.ui.activities.MainActivity
|
||||||
import com.github.libretube.ui.views.TimePickerPreference
|
import com.github.libretube.ui.views.TimePickerPreference
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The notification worker which checks for new streams in a certain frequency
|
* The notification worker which checks for new streams in a certain frequency
|
||||||
|
Loading…
Reference in New Issue
Block a user