Merge pull request #2442 from Bnyro/master

Improve the playlist cloning algorithm
This commit is contained in:
Bnyro 2022-12-19 16:58:59 +01:00 committed by GitHub
commit b98e6f5965
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 306 additions and 142 deletions

View File

@ -6,6 +6,7 @@ import com.github.libretube.R
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.PlaylistId
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.db.DatabaseHolder
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.util.PreferenceHelper
import com.github.libretube.util.ProxyHelper
import java.io.IOException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import retrofit2.HttpException
import java.io.IOException
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()
@ -110,16 +111,17 @@ object PlaylistsHelper {
return null
}
suspend fun addToPlaylist(playlistId: String, vararg videoIds: String): Boolean {
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean {
if (!loggedIn) {
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId }
for (videoId in videoIds) {
val localPlaylistItem = RetrofitInstance.api.getStreams(videoId).toLocalPlaylistItem(playlistId, videoId)
for (video in videos) {
val localPlaylistItem = video.toLocalPlaylistItem(playlistId)
awaitQuery {
// 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
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)
@ -128,7 +130,9 @@ object PlaylistsHelper {
// set the new playlist thumbnail URL
localPlaylistItem.thumbnailUrl?.let {
localPlaylist.playlist.thumbnailUrl = it
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(localPlaylist.playlist)
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(
localPlaylist.playlist
)
}
}
}
@ -140,7 +144,7 @@ object PlaylistsHelper {
token,
PlaylistId(
playlistId = playlistId,
videoIds = videoIds.toList()
videoIds = videos.toList().map { it.url!!.toID() }
)
).message == "ok"
}
@ -172,13 +176,19 @@ object PlaylistsHelper {
DatabaseHolder.Database.localPlaylistsDao().getAll()
}.first { it.playlist.id.toString() == playlistId }
awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(transaction.videos[index])
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(
transaction.videos[index]
)
}
if (transaction.videos.size > 1) {
if (index == 0) {
transaction.videos[1].thumbnailUrl?.let { transaction.playlist.thumbnailUrl = it }
transaction.videos[1].thumbnailUrl?.let {
transaction.playlist.thumbnailUrl = it
}
awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(transaction.playlist)
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(
transaction.playlist
)
}
}
return
@ -203,11 +213,27 @@ object PlaylistsHelper {
suspend fun importPlaylists(appContext: Context, playlists: List<ImportPlaylist>) {
for (playlist in playlists) {
val playlistId = createPlaylist(playlist.name!!, appContext) ?: continue
addToPlaylist(
playlistId,
*playlist.videos.map {
it.substring(it.length - 11, it.length)
}.toTypedArray()
// if logged in, add the playlists by their ID via an api call
val success: Boolean = if (loggedIn) {
addToPlaylist(
playlistId,
*playlist.videos.map {
StreamItem(url = it)
}.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(
newPlaylist,
*playlist.relatedStreams.orEmpty()
.map { it.url!!.toID() }
.toTypedArray()
*playlist.relatedStreams.orEmpty().toTypedArray()
)
var nextPage = playlist.nextpage
@ -264,9 +288,7 @@ object PlaylistsHelper {
RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage).apply {
addToPlaylist(
newPlaylist,
*relatedStreams.orEmpty()
.map { it.url!!.toID() }
.toTypedArray()
*relatedStreams.orEmpty().toTypedArray()
)
}.nextpage
} catch (e: Exception) {

View File

@ -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)) {
unsubscribe(channelId)
onUnsubscribe.invoke()

View File

@ -4,6 +4,8 @@ import kotlin.math.pow
import kotlin.math.roundToInt
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()
}

View File

@ -1,18 +1,18 @@
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
fun Streams.toLocalPlaylistItem(playlistId: String, videoId: String): LocalPlaylistItem {
fun StreamItem.toLocalPlaylistItem(playlistId: String): LocalPlaylistItem {
return LocalPlaylistItem(
playlistId = playlistId.toInt(),
videoId = videoId,
videoId = url!!.toID(),
title = title,
thumbnailUrl = thumbnailUrl,
uploader = uploader,
thumbnailUrl = thumbnail,
uploader = uploaderName,
uploaderUrl = uploaderUrl,
uploaderAvatar = uploaderAvatar,
uploadDate = uploadDate,
uploadDate = uploadedDate,
duration = duration
)
}

View File

@ -80,7 +80,9 @@ class UpdateService : Service() {
}
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)
return downloadsDir
}

View File

@ -286,7 +286,9 @@ class MainActivity : BaseActivity() {
searchViewModel.setQuery(null)
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
}
@ -300,8 +302,8 @@ class MainActivity : BaseActivity() {
}
// Handover back press to `BackPressedDispatcher`
else if (binding.bottomNav.menu.children.none {
it.itemId == navController.currentDestination?.id
}
it.itemId == navController.currentDestination?.id
}
) {
this@MainActivity.onBackPressedDispatcher.onBackPressed()
}

View File

@ -67,7 +67,10 @@ class CommentsAdapter(
}
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)
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) {
0 -> {
fetchReplies(nextPage) {

View File

@ -61,8 +61,14 @@ class DownloadsAdapter(
) { _, index ->
when (index) {
0 -> {
val audioDir = DownloadHelper.getDownloadDir(root.context, DownloadHelper.AUDIO_DIR)
val videoDir = DownloadHelper.getDownloadDir(root.context, DownloadHelper.VIDEO_DIR)
val audioDir = DownloadHelper.getDownloadDir(
root.context,
DownloadHelper.AUDIO_DIR
)
val videoDir = DownloadHelper.getDownloadDir(
root.context,
DownloadHelper.VIDEO_DIR
)
listOf(audioDir, videoDir).forEach {
val f = File(it, file.name)

View File

@ -20,10 +20,10 @@ import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
import com.github.libretube.ui.viewholders.PlaylistViewHolder
import com.github.libretube.util.ImageHelper
import com.github.libretube.util.NavigationHelper
import java.io.IOException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
class PlaylistAdapter(
private val videoFeed: MutableList<StreamItem>,

View File

@ -18,7 +18,11 @@ class PlaylistBookmarkAdapter(
private val bookmarkMode: BookmarkMode = BookmarkMode.FRAGMENT
) : RecyclerView.Adapter<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)
}
@ -40,7 +44,11 @@ class PlaylistBookmarkAdapter(
uploaderName.text = bookmark.uploader
root.setOnClickListener {
NavigationHelper.navigatePlaylist(root.context, bookmark.playlistId, PlaylistType.PUBLIC)
NavigationHelper.navigatePlaylist(
root.context,
bookmark.playlistId,
PlaylistType.PUBLIC
)
}
root.setOnLongClickListener {

View File

@ -102,7 +102,10 @@ class SearchAdapter(
val videoName = item.title!!
root.setOnLongClickListener {
VideoOptionsBottomSheet(videoId, videoName)
.show((root.context as BaseActivity).supportFragmentManager, VideoOptionsBottomSheet::class.java.name)
.show(
(root.context as BaseActivity).supportFragmentManager,
VideoOptionsBottomSheet::class.java.name
)
true
}
channelContainer.setOnClickListener {
@ -123,7 +126,10 @@ class SearchAdapter(
searchViews.text = root.context.getString(
R.string.subscribers,
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 {
NavigationHelper.navigateChannel(root.context, item.url)
}
@ -155,7 +161,10 @@ class SearchAdapter(
val playlistId = item.url!!.toID()
val playlistName = item.name!!
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
}
}

View File

@ -64,9 +64,15 @@ class VideosAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return when {
viewType == CAUGHT_UP_TYPE -> VideosViewHolder(AllCaughtUpRowBinding.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))
viewType == CAUGHT_UP_TYPE -> VideosViewHolder(
AllCaughtUpRowBinding.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(
PreferenceKeys.ALTERNATIVE_VIDEOS_LAYOUT,
false
@ -102,7 +108,9 @@ class VideosAdapter(
video.uploaderName + TextUtils.SEPARATOR +
video.views.formatShort() + " " +
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) }
channelImage.setOnClickListener {
NavigationHelper.navigateChannel(root.context, video.uploaderUrl)
@ -118,7 +126,10 @@ class VideosAdapter(
if (videoId == null || videoName == null) return@setOnLongClickListener true
VideoOptionsBottomSheet(videoId, videoName)
.show((root.context as BaseActivity).supportFragmentManager, VideoOptionsBottomSheet::class.java.name)
.show(
(root.context as BaseActivity).supportFragmentManager,
VideoOptionsBottomSheet::class.java.name
)
true
}
@ -134,7 +145,9 @@ class VideosAdapter(
videoInfo.text =
video.views.formatShort() + " " +
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 =
video.duration?.let { DateUtils.formatElapsedTime(it) }
@ -159,7 +172,10 @@ class VideosAdapter(
root.setOnLongClickListener {
if (videoId == null || videoName == null) return@setOnLongClickListener true
VideoOptionsBottomSheet(videoId, videoName)
.show((root.context as BaseActivity).supportFragmentManager, VideoOptionsBottomSheet::class.java.name)
.show(
(root.context as BaseActivity).supportFragmentManager,
VideoOptionsBottomSheet::class.java.name
)
true
}

View File

@ -59,7 +59,10 @@ class WatchHistoryAdapter(
}
root.setOnLongClickListener {
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
}

View File

@ -11,8 +11,10 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.databinding.DialogAddtoplaylistBinding
import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.toStreamItem
import com.github.libretube.extensions.toastFromMainThread
import com.github.libretube.ui.models.PlaylistViewModel
import com.github.libretube.util.ThemeHelper
@ -86,7 +88,10 @@ class AddToPlaylistDialog(
val appContext = context?.applicationContext ?: return
CoroutineScope(Dispatchers.IO).launch {
val success = try {
PlaylistsHelper.addToPlaylist(playlistId, videoId)
PlaylistsHelper.addToPlaylist(
playlistId,
RetrofitInstance.api.getStreams(videoId).toStreamItem(videoId)
)
} catch (e: Exception) {
Log.e(TAG(), e.toString())
appContext.toastFromMainThread(R.string.unknown_error)

View File

@ -38,7 +38,9 @@ class DeletePlaylistDialog(
if (playlistType == PlaylistType.LOCAL) {
awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistId)
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(playlistId)
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(
playlistId
)
}
onSuccess.invoke()
return

View File

@ -21,8 +21,8 @@ import com.github.libretube.util.ImageHelper
import com.github.libretube.util.MetadataHelper
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException
import java.io.IOException
import retrofit2.HttpException
class DownloadDialog(
private val videoId: String

View File

@ -20,7 +20,10 @@ class NavBarOptionsDialog : DialogFragment() {
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() {
override fun getMovementFlags(

View File

@ -27,11 +27,11 @@ import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.extensions.setupSubscriptionButton
import com.github.libretube.util.ImageHelper
import java.io.IOException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class ChannelFragment : BaseFragment() {
private lateinit var binding: FragmentChannelBinding
@ -134,7 +134,11 @@ class ChannelFragment : BaseFragment() {
if (isSubscribed == null) return@launchWhenCreated
runOnUiThread {
binding.channelSubscribe.setupSubscriptionButton(channelId, channelName, binding.notificationBell)
binding.channelSubscribe.setupSubscriptionButton(
channelId,
channelName,
binding.notificationBell
)
binding.channelShare.setOnClickListener {
val shareDialog = ShareDialog(

View File

@ -79,7 +79,11 @@ class HomeFragment : BaseFragment() {
if (feed.isEmpty()) return@runOrError
runOnUiThread {
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(
feed.toMutableList(),
forceMode = VideosAdapter.Companion.ForceMode.HOME
@ -108,17 +112,20 @@ class HomeFragment : BaseFragment() {
runOnUiThread {
makeVisible(binding.playlistsRV, binding.playlistsTV)
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 :
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
if (itemCount == 0) {
binding.playlistsRV.visibility = View.GONE
binding.playlistsTV.visibility = View.GONE
}
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
if (itemCount == 0) {
binding.playlistsRV.visibility = View.GONE
binding.playlistsTV.visibility = View.GONE
}
})
}
})
}
}
@ -129,7 +136,11 @@ class HomeFragment : BaseFragment() {
if (bookmarkedPlaylists.isEmpty()) return@runOrError
runOnUiThread {
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(
bookmarkedPlaylists,
PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME

View File

@ -128,13 +128,13 @@ class LibraryFragment : BaseFragment() {
// listen for playlists to become deleted
playlistsAdapter.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
binding.nothingHere.visibility =
if (playlistsAdapter.itemCount == 0) View.VISIBLE else View.GONE
super.onItemRangeRemoved(positionStart, itemCount)
}
})
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
binding.nothingHere.visibility =
if (playlistsAdapter.itemCount == 0) View.VISIBLE else View.GONE
super.onItemRangeRemoved(positionStart, itemCount)
}
})
binding.nothingHere.visibility = View.GONE
binding.playlistRecView.adapter = playlistsAdapter

View File

@ -105,15 +105,15 @@ import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.upstream.DefaultDataSource
import com.google.android.exoplayer2.util.MimeTypes
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.Dispatchers
import kotlinx.coroutines.launch
import org.chromium.net.CronetEngine
import retrofit2.HttpException
import java.io.IOException
import java.util.*
import java.util.concurrent.Executors
import kotlin.math.abs
class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
@ -1136,8 +1136,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
for (vid in videoStreams) {
if (resolutions.any {
it.resolution == vid.quality.qualityToInt()
} || vid.url == null
it.resolution == vid.quality.qualityToInt()
} || vid.url == null
) {
continue
}
@ -1160,7 +1160,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
if (resolutions.isEmpty()) {
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 {
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
val newParams = trackSelector.buildUponParameters()
if (PlayerHelper.defaultSubtitleCode != "" && subtitleCodesList.contains(PlayerHelper.defaultSubtitleCode)) {
if (PlayerHelper.defaultSubtitleCode != "" && subtitleCodesList.contains(
PlayerHelper.defaultSubtitleCode
)
) {
newParams
.setPreferredTextLanguage(PlayerHelper.defaultSubtitleCode)
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
@ -1464,7 +1471,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
return false
}
val backgroundModeRunning = BackgroundHelper.isServiceRunning(requireContext(), BackgroundMode::class.java)
val backgroundModeRunning = BackgroundHelper.isServiceRunning(
requireContext(),
BackgroundMode::class.java
)
return exoPlayer.isPlaying && !backgroundModeRunning
}

View File

@ -34,8 +34,8 @@ import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
import com.github.libretube.util.ImageHelper
import com.github.libretube.util.NavigationHelper
import com.github.libretube.util.TextUtils
import retrofit2.HttpException
import java.io.IOException
import retrofit2.HttpException
class PlaylistFragment : BaseFragment() {
private lateinit var binding: FragmentPlaylistBinding
@ -175,29 +175,29 @@ class PlaylistFragment : BaseFragment() {
// listen for playlist items to become deleted
playlistAdapter!!.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
if (positionStart == 0) {
ImageHelper.loadImage(
playlistFeed.firstOrNull()?.thumbnail ?: "",
binding.thumbnail
)
}
val info = binding.playlistInfo.text.split(TextUtils.SEPARATOR)
binding.playlistInfo.text = (
if (info.size == 2) {
info[0] + TextUtils.SEPARATOR
} else {
""
}
) + getString(
R.string.videoCount,
playlistAdapter!!.itemCount.toString()
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
if (positionStart == 0) {
ImageHelper.loadImage(
playlistFeed.firstOrNull()?.thumbnail ?: "",
binding.thumbnail
)
super.onItemRangeRemoved(positionStart, itemCount)
}
})
val info = binding.playlistInfo.text.split(TextUtils.SEPARATOR)
binding.playlistInfo.text = (
if (info.size == 2) {
info[0] + TextUtils.SEPARATOR
} else {
""
}
) + getString(
R.string.videoCount,
playlistAdapter!!.itemCount.toString()
)
super.onItemRangeRemoved(positionStart, itemCount)
}
})
binding.playlistRecView.adapter = playlistAdapter
binding.playlistScrollview.viewTreeObserver

View File

@ -18,8 +18,8 @@ import com.github.libretube.extensions.hideKeyboard
import com.github.libretube.ui.adapters.SearchAdapter
import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.util.PreferenceHelper
import retrofit2.HttpException
import java.io.IOException
import retrofit2.HttpException
class SearchResultFragment : BaseFragment() {
private lateinit var binding: FragmentSearchResultBinding

View File

@ -150,7 +150,9 @@ class SubscriptionsFragment : BaseFragment() {
// add an "all caught up item"
if (sortOrder == 0) {
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) {
sortedFeed.add(caughtUpIndex, StreamItem(type = "caught"))
}

View File

@ -17,8 +17,8 @@ import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.util.LocaleHelper
import com.google.android.material.snackbar.Snackbar
import retrofit2.HttpException
import java.io.IOException
import retrofit2.HttpException
class TrendsFragment : BaseFragment() {
private lateinit var binding: FragmentTrendsBinding

View File

@ -88,14 +88,14 @@ class WatchHistoryFragment : BaseFragment() {
// observe changes
watchHistoryAdapter.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
if (watchHistoryAdapter.itemCount == 0) {
binding.watchHistoryRecView.visibility = View.GONE
binding.historyEmpty.visibility = View.VISIBLE
}
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
if (watchHistoryAdapter.itemCount == 0) {
binding.watchHistoryRecView.visibility = View.GONE
binding.historyEmpty.visibility = View.VISIBLE
}
})
}
})
binding.watchHistoryRecView.adapter = watchHistoryAdapter
binding.historyEmpty.visibility = View.GONE

View File

@ -42,7 +42,10 @@ class AppearanceSettings : BasePreferenceFragment() {
}
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 {
changeIcon?.summary = getString(it.nameResource)
}

View File

@ -85,7 +85,11 @@ class MainSettings : BasePreferenceFragment() {
// checking for update: yes -> dialog, no -> snackBar
update?.setOnPreferenceClickListener {
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
}
CoroutineScope(Dispatchers.IO).launch {

View File

@ -40,9 +40,15 @@ class NotificationSettings : BasePreferenceFragment() {
true
}
val notificationTime = findPreference<SwitchPreferenceCompat>(PreferenceKeys.NOTIFICATION_TIME_ENABLED)
val notificationStartTime = findPreference<TimePickerPreference>(PreferenceKeys.NOTIFICATION_START_TIME)
val notificationEndTime = findPreference<TimePickerPreference>(PreferenceKeys.NOTIFICATION_END_TIME)
val notificationTime = findPreference<SwitchPreferenceCompat>(
PreferenceKeys.NOTIFICATION_TIME_ENABLED
)
val notificationStartTime = findPreference<TimePickerPreference>(
PreferenceKeys.NOTIFICATION_START_TIME
)
val notificationEndTime = findPreference<TimePickerPreference>(
PreferenceKeys.NOTIFICATION_END_TIME
)
listOf(notificationStartTime, notificationEndTime).forEach {
it?.isEnabled = notificationTime?.isChecked == true
}

View File

@ -28,7 +28,11 @@ class CommentsSheet(
private lateinit var commentsAdapter: CommentsAdapter
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)
// set a fixed maximum height
binding.root.maxHeight = maxHeight

View File

@ -244,8 +244,12 @@ internal class CustomExoPlayerView(
R.drawable.ic_aspect_ratio,
{
when (resizeMode) {
AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(R.string.resize_mode_fit)
AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(R.string.resize_mode_fill)
AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(
R.string.resize_mode_fit
)
AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(
R.string.resize_mode_fill
)
else -> context.getString(R.string.resize_mode_zoom)
}
}
@ -257,9 +261,9 @@ internal class CustomExoPlayerView(
R.drawable.ic_speed,
{
"${
player?.playbackParameters?.speed
.toString()
.replace(".0", "")
player?.playbackParameters?.speed
.toString()
.replace(".0", "")
}x"
}
) {
@ -403,7 +407,9 @@ internal class CustomExoPlayerView(
// If brightness progress goes to below 0, set to system brightness
if (distance <= 0) {
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)
return
}

View File

@ -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() {
super.onAttached()

View File

@ -52,7 +52,12 @@ class BrightnessHelper(private val activity: Activity) {
* Set current brightness value with scaling to given range.
* [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)
if (shouldSave) savedBrightness = brightness
}

View File

@ -2,14 +2,14 @@ package com.github.libretube.util
import com.github.libretube.api.obj.PipedStream
import com.github.libretube.api.obj.Streams
import org.w3c.dom.Document
import org.w3c.dom.Element
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
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
@ -66,7 +66,9 @@ object DashHelper {
for (stream in streams.audioStreams!!) {
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) {
adapSetInfo.formats.add(stream)
continue

View File

@ -13,9 +13,9 @@ import coil.request.CachePolicy
import coil.request.ImageRequest
import com.github.libretube.api.CronetHelper
import com.github.libretube.constants.PreferenceKeys
import okio.use
import java.io.File
import java.io.FileOutputStream
import okio.use
object ImageHelper {
lateinit var imageLoader: ImageLoader

View File

@ -15,11 +15,11 @@ import com.github.libretube.obj.ImportPlaylist
import com.github.libretube.obj.ImportPlaylistFile
import com.github.libretube.obj.NewPipeSubscription
import com.github.libretube.obj.NewPipeSubscriptions
import java.io.FileOutputStream
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.FileOutputStream
class ImportHelper(
private val activity: Activity
@ -56,7 +56,10 @@ class ImportHelper(
return when (val fileType = activity.contentResolver.getType(uri)) {
"application/json", "application/*", "application/octet-stream" -> {
// 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 {
it.url!!.replace("https://www.youtube.com/channel/", "")
}
@ -131,7 +134,10 @@ class ImportHelper(
}
}
"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())
}
else -> {

View File

@ -124,7 +124,13 @@ object PreferenceHelper {
fun toggleIgnorableNotificationChannel(channelId: String) {
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(
PreferenceKeys.IGNORED_NOTIFICATION_CHANNELS,
ignorableChannels.joinToString(",")

View File

@ -2,13 +2,13 @@ package com.github.libretube.util
import com.github.libretube.api.RetrofitInstance
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.URLDecoder
import java.nio.charset.StandardCharsets
import java.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
object ProxyHelper {
private fun getImageProxyUrl(): String? {

View File

@ -17,8 +17,8 @@ import com.github.libretube.extensions.toID
import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.views.TimePickerPreference
import com.github.libretube.util.PreferenceHelper
import kotlinx.coroutines.runBlocking
import java.time.LocalTime
import kotlinx.coroutines.runBlocking
/**
* The notification worker which checks for new streams in a certain frequency