Merge branch 'libre-tube:master' into master

This commit is contained in:
ձռօռყ_սռĸռօառ 2022-06-14 20:28:39 +05:30 committed by GitHub
commit 332c65e51b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 619 additions and 337 deletions

View File

@ -10,7 +10,7 @@ jobs:
with: with:
fetch-depth: 1 fetch-depth: 1
- name: ktlint - name: ktlint
uses: ScaCap/action-ktlint@1.3 uses: ScaCap/action-ktlint@1.4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-check reporter: github-pr-check

View File

@ -3,6 +3,7 @@ package com.github.libretube
import android.content.Context import android.content.Context
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import com.github.libretube.obj.Streams import com.github.libretube.obj.Streams
import com.github.libretube.util.DescriptionAdapter
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ExoPlayer
@ -10,6 +11,7 @@ import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.ui.PlayerNotificationManager
import gen._base._base_java__rjava_resources.srcjar.R.id.title
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -66,13 +68,20 @@ class BackgroundMode {
/** /**
* Initializes the [playerNotification] attached to the [player] and shows it. * Initializes the [playerNotification] attached to the [player] and shows it.
*/ */
private fun initializePlayerNotification(c: Context) { private fun initializePlayerNotification(c: Context, streams: Streams) {
playerNotification = PlayerNotificationManager playerNotification = PlayerNotificationManager
.Builder(c, 1, "background_mode").build() .Builder(c, 1, "background_mode")
playerNotification.setPlayer(player) // set the description of the notification
playerNotification.setUsePreviousAction(false) .setMediaDescriptionAdapter(
playerNotification.setUseNextAction(false) DescriptionAdapter(streams.title!!, streams.uploader!!, streams.thumbnailUrl!!)
playerNotification.setMediaSessionToken(mediaSession.sessionToken) )
.build()
playerNotification.apply {
setPlayer(player)
setUsePreviousAction(false)
setUseNextAction(false)
setMediaSessionToken(mediaSession.sessionToken)
}
} }
/** /**
@ -104,7 +113,7 @@ class BackgroundMode {
job.join() job.join()
initializePlayer(c) initializePlayer(c)
initializePlayerNotification(c) initializePlayerNotification(c, response!!)
player?.apply { player?.apply {
playWhenReady = playWhenReadyPlayer playWhenReady = playWhenReadyPlayer

View File

@ -10,6 +10,7 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
@ -68,6 +69,12 @@ class MainActivity : AppCompatActivity() {
sharedPreferences.getBoolean("sponsors_category_key", false) sharedPreferences.getBoolean("sponsors_category_key", false)
SponsorBlockSettings.outroEnabled = SponsorBlockSettings.outroEnabled =
sharedPreferences.getBoolean("outro_category_key", false) sharedPreferences.getBoolean("outro_category_key", false)
SponsorBlockSettings.fillerEnabled =
sharedPreferences.getBoolean("filler_category_key", false)
SponsorBlockSettings.musicOfftopicEnabled =
sharedPreferences.getBoolean("music_offtopic_category_key", false)
SponsorBlockSettings.previewEnabled =
sharedPreferences.getBoolean("preview_category_key", false)
ThemeHelper().updateTheme(this) ThemeHelper().updateTheme(this)
LocaleHelper().updateLanguage(this) LocaleHelper().updateLanguage(this)
@ -199,19 +206,12 @@ class MainActivity : AppCompatActivity() {
.replace("/embed/", "") .replace("/embed/", "")
val bundle = Bundle() val bundle = Bundle()
bundle.putString("videoId", watch) bundle.putString("videoId", watch)
val frag = PlayerFragment() // for time stamped links
frag.arguments = bundle if (data.query != null && data.query?.contains("t=")!!) {
supportFragmentManager.beginTransaction() val timeStamp = data.query.toString().split("t=")[1]
.remove(PlayerFragment()) bundle.putLong("timeStamp", timeStamp.toLong())
.commit() }
supportFragmentManager.beginTransaction() loadWatch(bundle)
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
} else if (data.path!!.contains("/watch") && data.query != null) { } else if (data.path!!.contains("/watch") && data.query != null) {
Log.d("dafaq", data.query!!) Log.d("dafaq", data.query!!)
var watch = data.query!! var watch = data.query!!
@ -226,39 +226,41 @@ class MainActivity : AppCompatActivity() {
} }
var bundle = Bundle() var bundle = Bundle()
bundle.putString("videoId", watch.replace("v=", "")) bundle.putString("videoId", watch.replace("v=", ""))
var frag = PlayerFragment() // for time stamped links
frag.arguments = bundle if (data.query != null && data.query?.contains("t=")!!) {
supportFragmentManager.beginTransaction() val timeStamp = data.query.toString().split("t=")[1]
.remove(PlayerFragment()) bundle.putLong("timeStamp", timeStamp.toLong())
.commit() }
supportFragmentManager.beginTransaction() loadWatch(bundle)
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
} else { } else {
var watch = data.path!!.replace("/", "") var watch = data.path!!.replace("/", "")
var bundle = Bundle() var bundle = Bundle()
bundle.putString("videoId", watch) bundle.putString("videoId", watch)
var frag = PlayerFragment() // for time stamped links
frag.arguments = bundle if (data.query != null && data.query?.contains("t=")!!) {
supportFragmentManager.beginTransaction() val timeStamp = data.query.toString().split("t=")[1]
.remove(PlayerFragment()) bundle.putLong("timeStamp", timeStamp.toLong())
.commit() }
supportFragmentManager.beginTransaction() loadWatch(bundle)
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
} }
} }
private fun loadWatch(bundle: Bundle) {
var frag = PlayerFragment()
frag.arguments = bundle
supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
Handler(Looper.getMainLooper()).postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
}
override fun onBackPressed() { override fun onBackPressed() {
try { try {
val mainMotionLayout = findViewById<MotionLayout>(R.id.mainMotionLayout) val mainMotionLayout = findViewById<MotionLayout>(R.id.mainMotionLayout)

View File

@ -8,14 +8,19 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.obj.StreamItem import com.github.libretube.obj.StreamItem
import com.github.libretube.util.formatShort import com.github.libretube.util.formatShort
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
class ChannelAdapter(private val videoFeed: MutableList<StreamItem>) : class ChannelAdapter(
private val videoFeed: MutableList<StreamItem>,
private val childFragmentManager: FragmentManager
) :
RecyclerView.Adapter<ChannelViewHolder>() { RecyclerView.Adapter<ChannelViewHolder>() {
override fun getItemCount(): Int { override fun getItemCount(): Int {
return videoFeed.size return videoFeed.size
@ -55,6 +60,12 @@ class ChannelAdapter(private val videoFeed: MutableList<StreamItem>) :
.replace(R.id.container, frag) .replace(R.id.container, frag)
.commitNow() .commitNow()
} }
holder.v.setOnLongClickListener {
val videoId = trending.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, holder.v.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
} }
} }

View File

@ -0,0 +1,48 @@
package com.github.libretube.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.obj.ChapterSegment
import com.google.android.exoplayer2.ExoPlayer
import com.squareup.picasso.Picasso
class ChaptersAdapter(
private val chapters: List<ChapterSegment>,
private val exoPlayer: ExoPlayer
) : RecyclerView.Adapter<ChaptersViewHolder>() {
val TAG = "ChaptersAdapter"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChaptersViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.chapter_column, parent, false)
return ChaptersViewHolder(cell)
}
override fun onBindViewHolder(holder: ChaptersViewHolder, position: Int) {
val chapter = chapters[position]
val chapterImage = holder.v.findViewById<ImageView>(R.id.chapter_image)
Picasso.get().load(chapter.image).fit().centerCrop().into(chapterImage)
val chapterTitle = holder.v.findViewById<TextView>(R.id.chapter_title)
chapterTitle.text = chapter.title
holder.v.setOnClickListener {
val chapterStart = chapter.start!!.toLong() * 1000 // multiply by thousand for ms -> s
exoPlayer.seekTo(chapterStart)
}
}
override fun getItemCount(): Int {
return chapters.size
}
}
class ChaptersViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}

View File

@ -67,7 +67,8 @@ class VideoOptionsDialog(private val videoId: String, context: Context) : Dialog
} }
2 -> { 2 -> {
val shareDialog = ShareDialog(videoId) val shareDialog = ShareDialog(videoId)
shareDialog.show(childFragmentManager, "ShareDialog") // using parentFragmentManager is important here
shareDialog.show(parentFragmentManager, "ShareDialog")
} }
else -> { else -> {
dialog.dismiss() dialog.dismiss()

View File

@ -218,7 +218,10 @@ class ChannelFragment : Fragment() {
val channelImage = view.findViewById<ImageView>(R.id.channel_image) val channelImage = view.findViewById<ImageView>(R.id.channel_image)
Picasso.get().load(response.bannerUrl).into(bannerImage) Picasso.get().load(response.bannerUrl).into(bannerImage)
Picasso.get().load(response.avatarUrl).into(channelImage) Picasso.get().load(response.avatarUrl).into(channelImage)
channelAdapter = ChannelAdapter(response.relatedStreams!!.toMutableList()) channelAdapter = ChannelAdapter(
response.relatedStreams!!.toMutableList(),
childFragmentManager
)
view.findViewById<RecyclerView>(R.id.channel_recView).adapter = channelAdapter view.findViewById<RecyclerView>(R.id.channel_recView).adapter = channelAdapter
} }
} }

View File

@ -3,7 +3,6 @@ package com.github.libretube.fragments
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.graphics.Rect import android.graphics.Rect
@ -18,9 +17,6 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import android.view.animation.RotateAnimation
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
@ -43,6 +39,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.IS_DOWNLOAD_RUNNING import com.github.libretube.IS_DOWNLOAD_RUNNING
import com.github.libretube.MainActivity import com.github.libretube.MainActivity
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.adapters.ChaptersAdapter
import com.github.libretube.adapters.CommentsAdapter import com.github.libretube.adapters.CommentsAdapter
import com.github.libretube.adapters.TrendingAdapter import com.github.libretube.adapters.TrendingAdapter
import com.github.libretube.dialogs.AddtoPlaylistDialog import com.github.libretube.dialogs.AddtoPlaylistDialog
@ -57,6 +54,7 @@ import com.github.libretube.obj.Streams
import com.github.libretube.obj.Subscribe import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.SponsorBlockSettings import com.github.libretube.preferences.SponsorBlockSettings
import com.github.libretube.util.CronetHelper import com.github.libretube.util.CronetHelper
import com.github.libretube.util.DescriptionAdapter
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.formatShort import com.github.libretube.util.formatShort
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
@ -102,7 +100,7 @@ class PlayerFragment : Fragment() {
private var whichQuality = 0 private var whichQuality = 0
private var isZoomed: Boolean = false private var isZoomed: Boolean = false
var isSubscribed: Boolean = false private var isSubscribed: Boolean = false
private lateinit var relatedRecView: RecyclerView private lateinit var relatedRecView: RecyclerView
private lateinit var commentsRecView: RecyclerView private lateinit var commentsRecView: RecyclerView
@ -122,6 +120,10 @@ class PlayerFragment : Fragment() {
private lateinit var mediaSessionConnector: MediaSessionConnector private lateinit var mediaSessionConnector: MediaSessionConnector
private lateinit var playerNotification: PlayerNotificationManager private lateinit var playerNotification: PlayerNotificationManager
private lateinit var title: String
private lateinit var uploader: String
private lateinit var thumbnailUrl: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
@ -142,6 +144,11 @@ class PlayerFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
hideKeyboard() hideKeyboard()
initializeTransitionLayout(view)
fetchJsonAndInitPlayer(view)
}
private fun initializeTransitionLayout(view: View) {
val playerDescription = view.findViewById<TextView>(R.id.player_description) val playerDescription = view.findViewById<TextView>(R.id.player_description)
videoId = videoId!!.replace("/watch?v=", "") videoId = videoId!!.replace("/watch?v=", "")
relDownloadVideo = view.findViewById(R.id.relPlayer_download) relDownloadVideo = view.findViewById(R.id.relPlayer_download)
@ -208,7 +215,7 @@ class PlayerFragment : Fragment() {
playerMotionLayout.progress = 1.toFloat() playerMotionLayout.progress = 1.toFloat()
playerMotionLayout.transitionToStart() playerMotionLayout.transitionToStart()
fetchJson(view)
view.findViewById<ImageView>(R.id.close_imageView).setOnClickListener { view.findViewById<ImageView>(R.id.close_imageView).setOnClickListener {
motionLayout.transitionToEnd() motionLayout.transitionToEnd()
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
@ -239,25 +246,11 @@ class PlayerFragment : Fragment() {
} }
view.findViewById<RelativeLayout>(R.id.player_title_layout).setOnClickListener { view.findViewById<RelativeLayout>(R.id.player_title_layout).setOnClickListener {
val image = view.findViewById<ImageView>(R.id.player_description_arrow)
image.animate().rotationBy(180F).setDuration(100).start()
if (playerDescription.isVisible) { if (playerDescription.isVisible) {
val image = view.findViewById<ImageView>(R.id.player_description_arrow)
image.clearAnimation()
playerDescription.visibility = View.GONE playerDescription.visibility = View.GONE
} else { } else {
// toggle button
val rotate = RotateAnimation(
0F,
180F,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
)
rotate.duration = 100
rotate.interpolator = LinearInterpolator()
rotate.fillAfter = true
val image = view.findViewById<ImageView>(R.id.player_description_arrow)
image.startAnimation(rotate)
playerDescription.visibility = View.VISIBLE playerDescription.visibility = View.VISIBLE
} }
} }
@ -319,8 +312,6 @@ class PlayerFragment : Fragment() {
commentsRecView.layoutManager = LinearLayoutManager(view.context) commentsRecView.layoutManager = LinearLayoutManager(view.context)
commentsRecView.setItemViewCacheSize(20) commentsRecView.setItemViewCacheSize(20)
commentsRecView.isDrawingCacheEnabled = true
commentsRecView.drawingCacheQuality = View.DRAWING_CACHE_QUALITY_HIGH
relatedRecView = view.findViewById(R.id.player_recView) relatedRecView = view.findViewById(R.id.player_recView)
relatedRecView.layoutManager = relatedRecView.layoutManager =
@ -372,7 +363,7 @@ class PlayerFragment : Fragment() {
} }
} }
private fun fetchJson(view: View) { private fun fetchJsonAndInitPlayer(view: View) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
@ -387,6 +378,40 @@ class PlayerFragment : Fragment() {
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} }
// for the notification description adapter
title = response.title!!
uploader = response.uploader!!
thumbnailUrl = response.thumbnailUrl!!
// check whether related streams are enabled
val sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(requireContext())
relatedStreamsEnabled = sharedPreferences.getBoolean("related_streams_toggle", true)
runOnUiThread {
createExoPlayer(view)
prepareExoPlayerView()
if (response.chapters != null) initializeChapters(response.chapters)
setResolutionAndSubtitles(view, response)
// support for time stamped links
if (arguments?.getLong("timeStamp") != null) {
val position = arguments?.getLong("timeStamp")!! * 1000
exoPlayer.seekTo(position)
}
exoPlayer.prepare()
exoPlayer.play()
initializePlayerView(view, response)
initializePlayerNotification(requireContext())
fetchSponsorBlockSegments()
if (!relatedStreamsEnabled) toggleComments()
}
}
}
run()
}
private fun fetchSponsorBlockSegments() {
fun run() {
lifecycleScope.launchWhenCreated {
if (SponsorBlockSettings.sponsorBlockEnabled) { if (SponsorBlockSettings.sponsorBlockEnabled) {
val categories: ArrayList<String> = arrayListOf() val categories: ArrayList<String> = arrayListOf()
if (SponsorBlockSettings.introEnabled) { if (SponsorBlockSettings.introEnabled) {
@ -404,6 +429,15 @@ class PlayerFragment : Fragment() {
if (SponsorBlockSettings.outroEnabled) { if (SponsorBlockSettings.outroEnabled) {
categories.add("outro") categories.add("outro")
} }
if (SponsorBlockSettings.fillerEnabled) {
categories.add("filler")
}
if (SponsorBlockSettings.musicOfftopicEnabled) {
categories.add("music_offtopic")
}
if (SponsorBlockSettings.previewEnabled) {
categories.add("preview")
}
if (categories.size > 0) { if (categories.size > 0) {
segmentData = try { segmentData = try {
@ -425,20 +459,6 @@ class PlayerFragment : Fragment() {
} }
} }
} }
// check whether related streams are enabled
val sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(requireContext())
relatedStreamsEnabled = sharedPreferences.getBoolean("related_streams_toggle", true)
runOnUiThread {
createExoPlayer(view)
prepareExoPlayerView()
if (response.chapters != null) initializeChapters(response.chapters)
setResolutionAndSubtitles(view, response)
exoPlayer.prepare()
exoPlayer.play()
initializePlayerView(view, response)
if (!relatedStreamsEnabled) toggleComments()
}
} }
} }
run() run()
@ -470,7 +490,7 @@ class PlayerFragment : Fragment() {
view.findViewById<TextView>(R.id.player_description).text = response.description view.findViewById<TextView>(R.id.player_description).text = response.description
// Listener for play and pause icon change // Listener for play and pause icon change
exoPlayer.addListener(object : com.google.android.exoplayer2.Player.Listener { exoPlayer.addListener(object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying && SponsorBlockSettings.sponsorBlockEnabled) { if (isPlaying && SponsorBlockSettings.sponsorBlockEnabled) {
exoPlayerView.postDelayed( exoPlayerView.postDelayed(
@ -479,7 +499,7 @@ class PlayerFragment : Fragment() {
) )
} }
} }
@Deprecated(message = "Deprecated", level = DeprecationLevel.HIDDEN)
override fun onPlayerStateChanged( override fun onPlayerStateChanged(
playWhenReady: Boolean, playWhenReady: Boolean,
playbackState: Int playbackState: Int
@ -520,7 +540,7 @@ class PlayerFragment : Fragment() {
relDownloadVideo.setOnClickListener { relDownloadVideo.setOnClickListener {
if (!IS_DOWNLOAD_RUNNING) { if (!IS_DOWNLOAD_RUNNING) {
val newFragment = DownloadDialog() val newFragment = DownloadDialog()
var bundle = Bundle() val bundle = Bundle()
bundle.putString("video_id", videoId) bundle.putString("video_id", videoId)
bundle.putParcelable("streams", response) bundle.putParcelable("streams", response)
newFragment.arguments = bundle newFragment.arguments = bundle
@ -588,7 +608,7 @@ class PlayerFragment : Fragment() {
isSubscribed(subButton, channelId!!) isSubscribed(subButton, channelId!!)
view.findViewById<LinearLayout>(R.id.save).setOnClickListener { view.findViewById<LinearLayout>(R.id.save).setOnClickListener {
val newFragment = AddtoPlaylistDialog() val newFragment = AddtoPlaylistDialog()
var bundle = Bundle() val bundle = Bundle()
bundle.putString("videoId", videoId) bundle.putString("videoId", videoId)
newFragment.arguments = bundle newFragment.arguments = bundle
newFragment.show(childFragmentManager, "AddToPlaylist") newFragment.show(childFragmentManager, "AddToPlaylist")
@ -597,8 +617,28 @@ class PlayerFragment : Fragment() {
} }
private fun initializeChapters(chapters: List<ChapterSegment>) { private fun initializeChapters(chapters: List<ChapterSegment>) {
chapters.forEach { chapter -> val chaptersToggle = view?.findViewById<LinearLayout>(R.id.chapters_toggle)
Log.e(TAG, chapter.title!!) val chaptersRecView = view?.findViewById<RecyclerView>(R.id.chapters_recView)
val chaptersToggleText = view?.findViewById<TextView>(R.id.chapters_toggle_text)
val chaptersToggleArrow = view?.findViewById<ImageView>(R.id.chapters_toggle_arrow)
if (chapters.isNotEmpty()) {
chaptersToggle?.visibility = View.VISIBLE
chaptersToggle?.setOnClickListener {
if (chaptersRecView?.isVisible!!) {
chaptersRecView?.visibility = View.GONE
chaptersToggleText?.text = getString(R.string.show_chapters)
} else {
chaptersRecView?.visibility = View.VISIBLE
chaptersToggleText?.text = getString(R.string.hide_chapters)
}
chaptersToggleArrow!!.animate().setDuration(100).rotationBy(180F).start()
}
chaptersRecView?.layoutManager =
LinearLayoutManager(this.context, LinearLayoutManager.HORIZONTAL, false)
chaptersRecView?.adapter = ChaptersAdapter(chapters, exoPlayer)
} }
} }
@ -609,7 +649,7 @@ class PlayerFragment : Fragment() {
val name = vid.quality + " " + vid.format val name = vid.quality + " " + vid.format
videosNameArray += name videosNameArray += name
} }
var subtitle = mutableListOf<SubtitleConfiguration>() val subtitle = mutableListOf<SubtitleConfiguration>()
if (response.subtitles!!.isNotEmpty()) { if (response.subtitles!!.isNotEmpty()) {
subtitle.add( subtitle.add(
SubtitleConfiguration.Builder(response.subtitles[0].url!!.toUri()) SubtitleConfiguration.Builder(response.subtitles[0].url!!.toUri())
@ -719,70 +759,69 @@ class PlayerFragment : Fragment() {
val builder: MaterialAlertDialogBuilder? = activity?.let { val builder: MaterialAlertDialogBuilder? = activity?.let {
MaterialAlertDialogBuilder(it) MaterialAlertDialogBuilder(it)
} }
var lastPosition = exoPlayer.currentPosition val lastPosition = exoPlayer.currentPosition
builder!!.setTitle(R.string.choose_quality_dialog) builder!!.setTitle(R.string.choose_quality_dialog)
.setItems( .setItems(
videosNameArray, videosNameArray
DialogInterface.OnClickListener { _, which -> ) { _, which ->
whichQuality = which whichQuality = which
if (response.subtitles.isNotEmpty()) { if (response.subtitles.isNotEmpty()) {
var subtitle = val subtitle =
mutableListOf<SubtitleConfiguration>() mutableListOf<SubtitleConfiguration>()
subtitle.add( subtitle.add(
SubtitleConfiguration.Builder( SubtitleConfiguration.Builder(
response.subtitles[0].url!!.toUri() response.subtitles[0].url!!.toUri()
)
.setMimeType(response.subtitles[0].mimeType!!) // The correct MIME type (required).
.setLanguage(response.subtitles[0].code) // The subtitle language (optional).
.build()
) )
} .setMimeType(response.subtitles[0].mimeType!!) // The correct MIME type (required).
if (which == 0) { .setLanguage(response.subtitles[0].code) // The subtitle language (optional).
val mediaItem: MediaItem = MediaItem.Builder()
.setUri(response.hls)
.setSubtitleConfigurations(subtitle)
.build() .build()
exoPlayer.setMediaItem(mediaItem) )
} else {
val dataSourceFactory: DataSource.Factory =
DefaultHttpDataSource.Factory()
val videoItem: MediaItem = MediaItem.Builder()
.setUri(response.videoStreams[which - 1].url)
.setSubtitleConfigurations(subtitle)
.build()
val videoSource: MediaSource =
DefaultMediaSourceFactory(dataSourceFactory)
.createMediaSource(videoItem)
var audioSource: MediaSource =
DefaultMediaSourceFactory(dataSourceFactory)
.createMediaSource(
fromUri(response.audioStreams!![0].url!!)
)
if (response.videoStreams[which - 1].quality == "720p" ||
response.videoStreams[which - 1].quality == "1080p" ||
response.videoStreams[which - 1].quality == "480p"
) {
audioSource =
ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(
fromUri(
response.audioStreams[
getMostBitRate(
response.audioStreams
)
].url!!
)
)
}
val mergeSource: MediaSource =
MergingMediaSource(videoSource, audioSource)
exoPlayer.setMediaSource(mergeSource)
}
exoPlayer.seekTo(lastPosition)
view.findViewById<TextView>(R.id.quality_text).text =
videosNameArray[which]
} }
) if (which == 0) {
val mediaItem: MediaItem = MediaItem.Builder()
.setUri(response.hls)
.setSubtitleConfigurations(subtitle)
.build()
exoPlayer.setMediaItem(mediaItem)
} else {
val dataSourceFactory: DataSource.Factory =
DefaultHttpDataSource.Factory()
val videoItem: MediaItem = MediaItem.Builder()
.setUri(response.videoStreams[which - 1].url)
.setSubtitleConfigurations(subtitle)
.build()
val videoSource: MediaSource =
DefaultMediaSourceFactory(dataSourceFactory)
.createMediaSource(videoItem)
var audioSource: MediaSource =
DefaultMediaSourceFactory(dataSourceFactory)
.createMediaSource(
fromUri(response.audioStreams!![0].url!!)
)
if (response.videoStreams[which - 1].quality == "720p" ||
response.videoStreams[which - 1].quality == "1080p" ||
response.videoStreams[which - 1].quality == "480p"
) {
audioSource =
ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(
fromUri(
response.audioStreams[
getMostBitRate(
response.audioStreams
)
].url!!
)
)
}
val mergeSource: MediaSource =
MergingMediaSource(videoSource, audioSource)
exoPlayer.setMediaSource(mergeSource)
}
exoPlayer.seekTo(lastPosition)
view.findViewById<TextView>(R.id.quality_text).text =
videosNameArray[which]
}
val dialog = builder.create() val dialog = builder.create()
dialog.show() dialog.show()
} }
@ -822,8 +861,6 @@ class PlayerFragment : Fragment() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val playbackSpeed = sharedPreferences.getString("playback_speed", "1F")?.toFloat() val playbackSpeed = sharedPreferences.getString("playback_speed", "1F")?.toFloat()
exoPlayer.setPlaybackSpeed(playbackSpeed!!) exoPlayer.setPlaybackSpeed(playbackSpeed!!)
initializePlayerNotification(requireContext())
} }
private fun initializePlayerNotification(c: Context) { private fun initializePlayerNotification(c: Context) {
@ -838,6 +875,9 @@ class PlayerFragment : Fragment() {
playerNotification = PlayerNotificationManager playerNotification = PlayerNotificationManager
.Builder(c, 1, "background_mode") .Builder(c, 1, "background_mode")
.setMediaDescriptionAdapter(
DescriptionAdapter(title, uploader, thumbnailUrl)
)
.build() .build()
playerNotification.apply { playerNotification.apply {

View File

@ -198,7 +198,6 @@ class SearchFragment : Fragment() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
isFetchingSearch = true isFetchingSearch = true
hideKeyboard() hideKeyboard()
Log.e("here", "here")
val response = try { val response = try {
RetrofitInstance.api.getSearchResults(query, apiSearchFilter) RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
} catch (e: IOException) { } catch (e: IOException) {

View File

@ -29,40 +29,42 @@ class AboutFragment : Fragment() {
val topBarText = activity?.findViewById<TextView>(R.id.topBar_textView) val topBarText = activity?.findViewById<TextView>(R.id.topBar_textView)
topBarText?.text = getString(R.string.about) topBarText?.text = getString(R.string.about)
val appVersion = view?.findViewById<TextView>(R.id.app_version) val appVersion = view.findViewById<TextView>(R.id.app_version)
appVersion.text = BuildConfig.VERSION_NAME appVersion.text = BuildConfig.VERSION_NAME
val website = view?.findViewById<LinearLayout>(R.id.website) val website = view.findViewById<LinearLayout>(R.id.website)
website?.setOnClickListener { website.setOnClickListener {
openLinkFromHref("https://libre-tube.github.io/") openLinkFromHref("https://libre-tube.github.io/")
} }
val authors = view?.findViewById<LinearLayout>(R.id.authors) val authors = view.findViewById<LinearLayout>(R.id.authors)
authors?.setOnClickListener { authors.setOnClickListener {
openLinkFromHref("https://github.com/libre-tube/LibreTube/graphs/contributors") openLinkFromHref("https://github.com/libre-tube/LibreTube/graphs/contributors")
} }
val donate = view?.findViewById<LinearLayout>(R.id.donate) val donate = view.findViewById<LinearLayout>(R.id.donate)
donate?.setOnClickListener { donate.setOnClickListener {
openLinkFromHref("https://libre-tube.github.io/#donate") openLinkFromHref("https://libre-tube.github.io/#donate")
} }
val contributing = view?.findViewById<LinearLayout>(R.id.contributing) val contributing = view.findViewById<LinearLayout>(R.id.contributing)
contributing?.setOnClickListener { contributing.setOnClickListener {
openLinkFromHref("https://github.com/libre-tube/LibreTube") openLinkFromHref("https://github.com/libre-tube/LibreTube")
} }
val license = view.findViewById<LinearLayout>(R.id.license) val license = view.findViewById<LinearLayout>(R.id.license)
license?.setOnClickListener { license.setOnClickListener {
val licenseString = view?.context?.assets!! val licenseString = view.context.assets
.open("gpl3.html").bufferedReader().use { .open("gpl3.html").bufferedReader().use {
it.readText() it.readText()
} }
val licenseHtml = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(licenseString, 1) val licenseHtml = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
else Html.fromHtml(licenseString) Html.fromHtml(licenseString, 1)
} else {
Html.fromHtml(licenseString)
}
MaterialAlertDialogBuilder(view?.context!!) MaterialAlertDialogBuilder(view.context!!)
.setPositiveButton(getString(R.string.okay)) { _, _ -> } .setPositiveButton(getString(R.string.okay)) { _, _ -> }
.setMessage(licenseHtml) .setMessage(licenseHtml)
.create() .create()
.show() .show()
true
} }
} }

View File

@ -13,7 +13,6 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityCompat.recreate
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -30,7 +29,6 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import org.chromium.base.ThreadUtils.runOnUiThread
import org.json.JSONObject import org.json.JSONObject
import org.json.JSONTokener import org.json.JSONTokener
import retrofit2.HttpException import retrofit2.HttpException

View File

@ -17,6 +17,9 @@ class SponsorBlockSettings : PreferenceFragmentCompat() {
var interactionEnabled: Boolean = false var interactionEnabled: Boolean = false
var introEnabled: Boolean = false var introEnabled: Boolean = false
var outroEnabled: Boolean = false var outroEnabled: Boolean = false
var fillerEnabled: Boolean = false
var musicOfftopicEnabled: Boolean = false
var previewEnabled: Boolean = false
} }
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -65,5 +68,23 @@ class SponsorBlockSettings : PreferenceFragmentCompat() {
outroEnabled = newValue as Boolean outroEnabled = newValue as Boolean
true true
} }
val fillerToggle = findPreference<SwitchPreferenceCompat>("filler_category_key")
fillerToggle?.setOnPreferenceChangeListener { _, newValue ->
fillerEnabled = newValue as Boolean
true
}
val musicToggle = findPreference<SwitchPreferenceCompat>("music_offtopic_category_key")
musicToggle?.setOnPreferenceChangeListener { _, newValue ->
musicOfftopicEnabled = newValue as Boolean
true
}
val previewToggle = findPreference<SwitchPreferenceCompat>("preview_category_key")
previewToggle?.setOnPreferenceChangeListener { _, newValue ->
previewEnabled = newValue as Boolean
true
}
} }
} }

View File

@ -0,0 +1,55 @@
package com.github.libretube.util
import android.app.PendingIntent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import java.net.URL
// used to show title and thumbnail of the video in the notification
class DescriptionAdapter(
private val title: String,
private val channelName: String,
private val thumbnailUrl: String
) :
PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): CharSequence {
// return controller.metadata.description.title.toString()
return title
}
override fun createCurrentContentIntent(player: Player): PendingIntent? {
// return controller.sessionActivity
return null
}
override fun getCurrentContentText(player: Player): CharSequence? {
// return controller.metadata.description.subtitle.toString()
return channelName
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
lateinit var bitmap: Bitmap
val thread = Thread {
try {
// try to parse the thumbnailUrl to a Bitmap
val inputStream = URL(thumbnailUrl).openStream()
bitmap = BitmapFactory.decodeStream(inputStream)
} catch (ex: java.lang.Exception) {
ex.printStackTrace()
}
}
thread.start()
thread.join()
// return bitmap if initialized
return try {
bitmap
} catch (e: Exception) {
null
}
}
}

View File

@ -3,9 +3,9 @@
android:height="144dp" android:height="144dp"
android:viewportWidth="143" android:viewportWidth="143"
android:viewportHeight="144" android:viewportHeight="144"
android:tint="?android:attr/colorControlNormal" > android:tint="?android:attr/colorControlNormal">
<path <path
android:pathData="M35.74,87.66L35.74,41.39c0,-1.44 0.54,-2.7 1.61,-3.78 1.07,-1.08 2.32,-1.62 3.75,-1.62h45.95v5.4L41.11,41.39L41.11,87.66ZM46.47,98.47L46.47,52.2c0,-1.44 0.54,-2.7 1.61,-3.78 1.07,-1.08 2.32,-1.62 3.75,-1.62h45.95v5.4L51.83,52.2L51.83,98.47ZM101.89,108.01L62.56,108.01c-1.43,0 -2.68,-0.54 -3.75,-1.62 -1.07,-1.08 -1.61,-2.34 -1.61,-3.78L57.2,63c0,-1.44 0.54,-2.7 1.61,-3.78 1.07,-1.08 2.32,-1.62 3.75,-1.62h39.33c1.43,0 2.68,0.54 3.75,1.62 1.07,1.08 1.61,2.34 1.61,3.78v39.61c0,1.44 -0.54,2.7 -1.61,3.78 -1.07,1.08 -2.32,1.62 -3.75,1.62zM101.89,102.61L101.89,63L62.56,63L62.56,102.61ZM62.56,63v39.61z" android:pathData="M35.74,87.66L35.74,41.39c0,-1.44 0.54,-2.7 1.61,-3.78 1.07,-1.08 2.32,-1.62 3.75,-1.62h45.95v5.4L41.11,41.39L41.11,87.66ZM46.47,98.47L46.47,52.2c0,-1.44 0.54,-2.7 1.61,-3.78 1.07,-1.08 2.32,-1.62 3.75,-1.62h45.95v5.4L51.83,52.2L51.83,98.47ZM101.89,108.01L62.56,108.01c-1.43,0 -2.68,-0.54 -3.75,-1.62 -1.07,-1.08 -1.61,-2.34 -1.61,-3.78L57.2,63c0,-1.44 0.54,-2.7 1.61,-3.78 1.07,-1.08 2.32,-1.62 3.75,-1.62h39.33c1.43,0 2.68,0.54 3.75,1.62 1.07,1.08 1.61,2.34 1.61,3.78v39.61c0,1.44 -0.54,2.7 -1.61,3.78 -1.07,1.08 -2.32,1.62 -3.75,1.62zM101.89,102.61L101.89,63L62.56,63L62.56,102.61ZM62.56,63v39.61z"
android:strokeWidth="1" android:strokeWidth="1"
android:fillColor="#120807"/> android:fillColor="#120807" />
</vector> </vector>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_marginHorizontal="10dp"
android:layout_width="80dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/chapter_image"
android:layout_width="80dp"
android:layout_height="45dp"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/chapter_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:maxLines="3"
android:ellipsize="end"
android:text="Title"
android:textSize="13sp" />
</LinearLayout>

View File

@ -9,105 +9,106 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/commentor_image"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="16dp"
app:srcCompat="@mipmap/ic_launcher" />
<LinearLayout <LinearLayout
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:paddingBottom="16dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/commentor_image"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="16dp"
app:srcCompat="@mipmap/ic_launcher" />
<LinearLayout <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/comment_infos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:text="Author and Time"
android:textSize="15sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/verified_imageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
app:srcCompat="@drawable/ic_verified"
android:visibility="gone" />
<ImageView
android:id="@+id/pinned_imageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
app:srcCompat="@drawable/ic_pinned"
android:visibility="gone" />
</LinearLayout>
<TextView
android:id="@+id/comment_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="4dp" android:orientation="vertical">
android:autoLink="web"
android:text="Comment Text" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <TextView
android:id="@+id/likes_imageView" android:id="@+id/comment_infos"
android:layout_width="16dp" android:layout_width="wrap_content"
android:layout_height="16dp" android:layout_height="wrap_content"
android:layout_marginRight="6dp" android:ellipsize="end"
android:layout_marginTop="2dp" android:maxLines="2"
app:srcCompat="@drawable/ic_thumb_up" /> android:text="Author and Time"
android:textSize="15sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/verified_imageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
app:srcCompat="@drawable/ic_verified"
android:visibility="gone" />
<ImageView
android:id="@+id/pinned_imageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
app:srcCompat="@drawable/ic_pinned"
android:visibility="gone" />
</LinearLayout>
<TextView <TextView
android:id="@+id/likes_textView" android:id="@+id/comment_text"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="LikeCount" /> android:layout_marginBottom="4dp"
android:autoLink="web"
android:text="Comment Text" />
<ImageView <LinearLayout
android:id="@+id/hearted_imageView" android:layout_width="match_parent"
android:layout_width="14dp" android:layout_height="match_parent"
android:layout_height="14dp" android:orientation="horizontal">
android:layout_marginLeft="8dp"
android:layout_marginTop="3dp" <ImageView
app:srcCompat="@drawable/ic_hearted" android:id="@+id/likes_imageView"
android:visibility="gone" /> android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginRight="6dp"
android:layout_marginTop="2dp"
app:srcCompat="@drawable/ic_thumb_up" />
<TextView
android:id="@+id/likes_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LikeCount" />
<ImageView
android:id="@+id/hearted_imageView"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="3dp"
app:srcCompat="@drawable/ic_hearted"
android:visibility="gone" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/replies_recView" android:id="@+id/replies_recView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:nestedScrollingEnabled="false" /> android:nestedScrollingEnabled="false" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -39,7 +39,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="20dp" android:paddingVertical="10dp"> android:paddingHorizontal="20dp"
android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -59,7 +60,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="20dp" android:paddingVertical="10dp"> android:paddingHorizontal="20dp"
android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -79,7 +81,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="20dp" android:paddingVertical="10dp"> android:paddingHorizontal="20dp"
android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -99,7 +102,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="20dp" android:paddingVertical="10dp"> android:paddingHorizontal="20dp"
android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -119,7 +123,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="20dp" android:paddingVertical="10dp"> android:paddingHorizontal="20dp"
android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -26,15 +26,16 @@
android:orientation="vertical"> android:orientation="vertical">
<RelativeLayout <RelativeLayout
android:id="@+id/player_title_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/player_title_layout"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:id="@+id/player_title" android:id="@+id/player_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginEnd="30dp" android:layout_marginEnd="30dp"
@ -54,14 +55,54 @@
</RelativeLayout> </RelativeLayout>
<TextView <RelativeLayout
android:id="@+id/player_views_info"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="10dp" android:layout_marginHorizontal="10dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:orientation="horizontal">
<TextView
android:id="@+id/player_views_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10M views 2 days ago " />
<LinearLayout
android:id="@+id/chapters_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:id="@+id/chapters_toggle_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show_chapters" />
<ImageView
android:id="@+id/chapters_toggle_arrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="bottom"
android:src="@drawable/ic_arrow_down" />
</LinearLayout>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chapters_recView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/comments_toggle"
android:layout_marginLeft="10dp"
android:layout_marginTop="20dp"
android:layout_marginRight="10dp" android:layout_marginRight="10dp"
android:text="10M views 2 days ago " /> android:nestedScrollingEnabled="false"
android:visibility="gone" />
<TextView <TextView
android:id="@+id/player_description" android:id="@+id/player_description"
@ -146,10 +187,10 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autoSizeTextType="uniform"
android:gravity="center" android:gravity="center"
android:text="@string/download"
android:maxLines="1" android:maxLines="1"
android:autoSizeTextType="uniform" /> android:text="@string/download" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@ -203,8 +244,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="15dp" android:layout_marginTop="15dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="15dp" android:layout_marginBottom="15dp"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:paddingLeft="8dp" android:paddingLeft="8dp"
@ -294,10 +335,10 @@
android:id="@+id/comments_recView" android:id="@+id/comments_recView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_below="@id/comments_toggle" android:layout_below="@id/comments_toggle"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:layout_marginRight="20dp"
android:nestedScrollingEnabled="false" android:nestedScrollingEnabled="false"
android:visibility="gone" /> android:visibility="gone" />

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:paddingVertical="6dp" android:paddingVertical="8dp"
android:background="?android:attr/selectableItemBackground"> android:background="?android:attr/selectableItemBackground">
<ImageView <ImageView

View File

@ -100,6 +100,12 @@
<string name="category_intro_description">An interval without actual content. Could be a pause, static frame, repeating animation. Should not be used for transitions containing info.</string> <string name="category_intro_description">An interval without actual content. Could be a pause, static frame, repeating animation. Should not be used for transitions containing info.</string>
<string name="category_outro">End cards and credits</string> <string name="category_outro">End cards and credits</string>
<string name="category_outro_description">Info following the ending. Not for conclusions with info.</string> <string name="category_outro_description">Info following the ending. Not for conclusions with info.</string>
<string name="category_filler">Filler Tangent/Jokes</string>
<string name="category_filler_description">This is for tangential scenes added only for filler or humor that are not required to understand the main content of the video.</string>
<string name="category_music_offtopic">Music: Non-Music Section</string>
<string name="category_music_offtopic_description">This is only for use in music videos. It should cover parts of the video not part of official mixes. In the end, the video should resemble the Spotify or any other mix version as closely as possible, or should reduce talking or other distractions.</string>
<string name="category_preview">Preview/Recap</string>
<string name="category_preview_description">For segments that show what is coming up in this or future videos of the same series, but do not provide additional information. If it includes clips that only appear here, this is very likely not the right category.</string>
<string name="license">License</string> <string name="license">License</string>
<string name="color_accent">Accents</string> <string name="color_accent">Accents</string>
<string name="color_red">Resting red</string> <string name="color_red">Resting red</string>
@ -173,4 +179,6 @@
<string name="about_summary">Get to know team LibreTube and how it all happens.</string> <string name="about_summary">Get to know team LibreTube and how it all happens.</string>
<string name="related_streams">Related streams</string> <string name="related_streams">Related streams</string>
<string name="related_streams_summary">Show related streams to videos.</string> <string name="related_streams_summary">Show related streams to videos.</string>
<string name="show_chapters">Show chapters</string>
<string name="hide_chapters">Hide chapters</string>
</resources> </resources>

View File

@ -5,21 +5,21 @@
<PreferenceCategory app:title="@string/player"> <PreferenceCategory app:title="@string/player">
<ListPreference <ListPreference
app:title="@string/defres" android:icon="@drawable/ic_hd"
app:key="default_res" app:defaultValue=""
app:entries="@array/defres" app:entries="@array/defres"
app:entryValues="@array/defresValue" app:entryValues="@array/defresValue"
app:defaultValue="" app:key="default_res"
android:icon="@drawable/ic_hd" app:title="@string/defres"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
app:title="@string/playback_speed" android:icon="@drawable/ic_play"
app:key="playback_speed" app:defaultValue="1F"
app:entries="@array/playbackSpeed" app:entries="@array/playbackSpeed"
app:entryValues="@array/playbackSpeedValues" app:entryValues="@array/playbackSpeedValues"
app:defaultValue="1F" app:key="playback_speed"
android:icon="@drawable/ic_play" app:title="@string/playback_speed"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
</PreferenceCategory> </PreferenceCategory>
@ -27,55 +27,44 @@
<PreferenceCategory app:title="@string/downloads"> <PreferenceCategory app:title="@string/downloads">
<ListPreference <ListPreference
app:key="video_format" app:defaultValue=".mp4"
app:title="@string/video_format"
app:entries="@array/videoFormats" app:entries="@array/videoFormats"
app:entryValues="@array/videoFormatsValues" app:entryValues="@array/videoFormatsValues"
app:defaultValue=".mp4" app:icon="@drawable/ic_videocam"
app:key="video_format"
app:summary="@string/video_format_summary" app:summary="@string/video_format_summary"
app:icon="@drawable/ic_videocam" /> app:title="@string/video_format" />
<ListPreference <ListPreference
app:key="download_location"
app:title="@string/download_directory"
app:summary="@string/download_directory_summary"
android:defaultValue="downloads" android:defaultValue="downloads"
android:entries="@array/downloadLocation" android:entries="@array/downloadLocation"
android:entryValues="@array/downloadLocationValues" android:entryValues="@array/downloadLocationValues"
app:icon="@drawable/ic_download" /> app:icon="@drawable/ic_download"
app:key="download_location"
app:summary="@string/download_directory_summary"
app:title="@string/download_directory" />
<EditTextPreference <EditTextPreference
app:key="download_folder" android:defaultValue="LibreTube"
app:title="@string/download_folder"
app:summary="@string/download_folder_summary"
app:icon="@drawable/ic_folder" app:icon="@drawable/ic_folder"
android:defaultValue="LibreTube" /> app:key="download_folder"
app:summary="@string/download_folder_summary"
app:title="@string/download_folder" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="@string/search_history"> <PreferenceCategory app:title="@string/search_history">
<SwitchPreference <SwitchPreference
app:title="@string/search_history"
app:key="search_history_toggle"
android:defaultValue="true" android:defaultValue="true"
android:icon="@drawable/ic_history" /> android:icon="@drawable/ic_history"
app:key="search_history_toggle"
app:title="@string/search_history" />
<Preference <Preference
app:title="@string/clear_history" android:icon="@drawable/ic_trash"
app:key="clear_history" app:key="clear_history"
android:icon="@drawable/ic_trash" /> app:title="@string/clear_history" />
</PreferenceCategory>
<PreferenceCategory>
<SwitchPreference
app:key="related_streams_toggle"
app:title="@string/related_streams"
app:summary="@string/related_streams_summary"
android:defaultValue="true"
android:icon="@drawable/ic_list"/>
</PreferenceCategory> </PreferenceCategory>

View File

@ -5,30 +5,30 @@
<PreferenceCategory app:title="@string/appearance"> <PreferenceCategory app:title="@string/appearance">
<ListPreference <ListPreference
app:title="@string/app_theme" android:icon="@drawable/ic_theme"
app:key="theme_togglee" app:defaultValue="A"
app:entries="@array/themes" app:entries="@array/themes"
app:entryValues="@array/themesValue" app:entryValues="@array/themesValue"
app:defaultValue="A" app:key="theme_togglee"
android:icon="@drawable/ic_theme" app:title="@string/app_theme"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
app:title="@string/color_accent" android:icon="@drawable/ic_color"
app:key="accent_color" app:defaultValue="red"
app:entries="@array/accents" app:entries="@array/accents"
app:entryValues="@array/accentsValue" app:entryValues="@array/accentsValue"
app:defaultValue="red" app:key="accent_color"
android:icon="@drawable/ic_color" app:title="@string/color_accent"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
app:title="@string/app_icon" android:icon="@drawable/ic_frame"
app:key="icon_change" app:defaultValue="MainActivity"
app:entries="@array/icons" app:entries="@array/icons"
app:entryValues="@array/iconsValue" app:entryValues="@array/iconsValue"
app:defaultValue="MainActivity" app:key="icon_change"
android:icon="@drawable/ic_frame" app:title="@string/app_icon"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
</PreferenceCategory> </PreferenceCategory>
@ -36,23 +36,34 @@
<PreferenceCategory app:title="@string/app_behavior"> <PreferenceCategory app:title="@string/app_behavior">
<ListPreference <ListPreference
app:title="@string/defaultTab" android:icon="@drawable/ic_home"
app:key="default_tab" app:defaultValue="home"
app:entries="@array/tabs" app:entries="@array/tabs"
app:entryValues="@array/tabsValue" app:entryValues="@array/tabsValue"
app:defaultValue="home" app:key="default_tab"
android:icon="@drawable/ic_home" app:title="@string/defaultTab"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
app:title="@string/grid" android:icon="@drawable/ic_grid"
app:key="grid" app:defaultValue="@integer/grid_items"
app:entries="@array/grid" app:entries="@array/grid"
app:entryValues="@array/grid" app:entryValues="@array/grid"
app:defaultValue="@integer/grid_items" app:key="grid"
android:icon="@drawable/ic_grid" app:title="@string/grid"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory>
<SwitchPreference
android:defaultValue="true"
android:icon="@drawable/ic_list"
app:key="related_streams_toggle"
app:summary="@string/related_streams_summary"
app:title="@string/related_streams" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@ -41,6 +41,21 @@
app:title="@string/category_outro" app:title="@string/category_outro"
app:summary="@string/category_outro_description" /> app:summary="@string/category_outro_description" />
<SwitchPreferenceCompat
app:key="filler_category_key"
app:title="@string/category_filler"
app:summary="@string/category_filler_description" />
<SwitchPreferenceCompat
app:key="music_offtopic_category_key"
app:title="@string/category_music_offtopic"
app:summary="@string/category_music_offtopic_description" />
<SwitchPreferenceCompat
app:key="preview_category_key"
app:title="@string/category_preview"
app:summary="@string/category_preview_description" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@ -6,7 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.1' classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files