mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 14:20:30 +05:30
Merge pull request #2678 from Bnyro/audio-player
Integrated audio player UI
This commit is contained in:
commit
0632ce05d0
@ -8,7 +8,7 @@ object IntentData {
|
||||
const val timeStamp = "timeStamp"
|
||||
const val position = "position"
|
||||
const val fileName = "fileName"
|
||||
const val openQueueOnce = "openQueue"
|
||||
const val keepQueue = "keepQueue"
|
||||
const val playlistType = "playlistType"
|
||||
const val openAudioPlayer = "openAudioPlayer"
|
||||
}
|
||||
|
@ -93,7 +93,6 @@ object PreferenceKeys {
|
||||
* Background mode
|
||||
*/
|
||||
const val BACKGROUND_PLAYBACK_SPEED = "background_playback_speed"
|
||||
const val NOTIFICATION_OPEN_QUEUE = "notification_open_queue"
|
||||
|
||||
/**
|
||||
* Notifications
|
||||
|
@ -5,6 +5,7 @@ import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
@ -87,6 +88,16 @@ class BackgroundMode : Service() {
|
||||
*/
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
/**
|
||||
* Used for connecting to the AudioPlayerFragment
|
||||
*/
|
||||
private val binder = LocalBinder()
|
||||
|
||||
/**
|
||||
* Listener for passing playback state changes to the AudioPlayerFragment
|
||||
*/
|
||||
var onIsPlayingChanged: ((isPlaying: Boolean) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Setting the required [Notification] for running as a foreground service
|
||||
*/
|
||||
@ -251,6 +262,11 @@ class BackgroundMode : Service() {
|
||||
* Plays the next video when the current one ended
|
||||
*/
|
||||
player?.addListener(object : Player.Listener {
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
super.onIsPlayingChanged(isPlaying)
|
||||
onIsPlayingChanged?.invoke(isPlaying)
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(state: Int) {
|
||||
when (state) {
|
||||
Player.STATE_ENDED -> {
|
||||
@ -381,7 +397,26 @@ class BackgroundMode : Service() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
return null
|
||||
inner class LocalBinder : Binder() {
|
||||
// Return this instance of [BackgroundMode] so clients can call public methods
|
||||
fun getService(): BackgroundMode = this@BackgroundMode
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
fun getCurrentPosition() = player?.currentPosition
|
||||
|
||||
fun getDuration() = player?.duration
|
||||
|
||||
fun seekToPosition(position: Long) = player?.seekTo(position)
|
||||
|
||||
fun pause() {
|
||||
player?.pause()
|
||||
}
|
||||
|
||||
fun play() {
|
||||
player?.play()
|
||||
}
|
||||
}
|
||||
|
@ -39,11 +39,9 @@ import com.github.libretube.ui.fragments.PlayerFragment
|
||||
import com.github.libretube.ui.models.PlayerViewModel
|
||||
import com.github.libretube.ui.models.SearchViewModel
|
||||
import com.github.libretube.ui.models.SubscriptionsViewModel
|
||||
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
||||
import com.github.libretube.ui.tools.BreakReminder
|
||||
import com.github.libretube.util.NavBarHelper
|
||||
import com.github.libretube.util.NetworkHelper
|
||||
import com.github.libretube.util.PlayingQueue
|
||||
import com.github.libretube.util.PreferenceHelper
|
||||
import com.github.libretube.util.ThemeHelper
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
@ -221,11 +219,6 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
||||
menu?.findItem(R.id.action_queue)?.isVisible = PlayingQueue.isNotEmpty()
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the notification badge showing the amount of new videos
|
||||
*/
|
||||
@ -373,10 +366,6 @@ class MainActivity : BaseActivity() {
|
||||
startActivity(communityIntent)
|
||||
true
|
||||
}
|
||||
R.id.action_queue -> {
|
||||
PlayingQueueSheet().show(supportFragmentManager, null)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@ -391,6 +380,11 @@ class MainActivity : BaseActivity() {
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
if (intent?.getBooleanExtra(IntentData.openAudioPlayer, false) == true) {
|
||||
navController.navigate(R.id.audioPlayerFragment)
|
||||
return
|
||||
}
|
||||
|
||||
intent?.getStringExtra(IntentData.channelId)?.let {
|
||||
navController.navigate(
|
||||
R.id.channelFragment,
|
||||
@ -412,6 +406,7 @@ class MainActivity : BaseActivity() {
|
||||
intent?.getStringExtra(IntentData.videoId)?.let {
|
||||
loadVideo(it, intent?.getLongExtra(IntentData.timeStamp, 0L))
|
||||
}
|
||||
|
||||
when (intent?.getStringExtra("fragmentToOpen")) {
|
||||
"home" ->
|
||||
navController.navigate(R.id.homeFragment)
|
||||
@ -422,10 +417,6 @@ class MainActivity : BaseActivity() {
|
||||
"library" ->
|
||||
navController.navigate(R.id.libraryFragment)
|
||||
}
|
||||
if (intent?.getBooleanExtra(IntentData.openQueueOnce, false) == true) {
|
||||
PlayingQueueSheet()
|
||||
.show(supportFragmentManager)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVideo(videoId: String, timeStamp: Long?) {
|
||||
|
@ -0,0 +1,181 @@
|
||||
package com.github.libretube.ui.fragments
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.text.format.DateUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.api.obj.StreamItem
|
||||
import com.github.libretube.databinding.FragmentAudioPlayerBinding
|
||||
import com.github.libretube.extensions.toID
|
||||
import com.github.libretube.services.BackgroundMode
|
||||
import com.github.libretube.ui.activities.MainActivity
|
||||
import com.github.libretube.ui.base.BaseFragment
|
||||
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
||||
import com.github.libretube.util.ImageHelper
|
||||
import com.github.libretube.util.NavigationHelper
|
||||
import com.github.libretube.util.PlayingQueue
|
||||
|
||||
class AudioPlayerFragment : BaseFragment() {
|
||||
private lateinit var binding: FragmentAudioPlayerBinding
|
||||
private val onTrackChangeListener: (StreamItem) -> Unit = {
|
||||
updateStreamInfo()
|
||||
}
|
||||
private var handler = Handler(Looper.getMainLooper())
|
||||
private var isPaused: Boolean = false
|
||||
|
||||
private lateinit var playerService: BackgroundMode
|
||||
|
||||
/** Defines callbacks for service binding, passed to bindService() */
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||
val binder = service as BackgroundMode.LocalBinder
|
||||
playerService = binder.getService()
|
||||
handleServiceConnection()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||
val mainActivity = activity as MainActivity
|
||||
if (mainActivity.navController.currentDestination?.id == R.id.audioPlayerFragment) {
|
||||
mainActivity.navController.popBackStack()
|
||||
} else {
|
||||
mainActivity.navController.backQueue.removeAll {
|
||||
it.destination.id == R.id.audioPlayerFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Intent(activity, BackgroundMode::class.java).also { intent ->
|
||||
activity?.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentAudioPlayerBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.prev.setOnClickListener {
|
||||
val currentIndex = PlayingQueue.currentIndex()
|
||||
if (!PlayingQueue.hasPrev()) return@setOnClickListener
|
||||
PlayingQueue.onQueueItemSelected(currentIndex - 1)
|
||||
}
|
||||
|
||||
binding.next.setOnClickListener {
|
||||
val currentIndex = PlayingQueue.currentIndex()
|
||||
if (!PlayingQueue.hasNext()) return@setOnClickListener
|
||||
PlayingQueue.onQueueItemSelected(currentIndex + 1)
|
||||
}
|
||||
|
||||
binding.thumbnail.setOnClickListener {
|
||||
PlayingQueueSheet().show(childFragmentManager)
|
||||
}
|
||||
|
||||
// Listen for track changes due to autoplay or the notification
|
||||
PlayingQueue.addOnTrackChangedListener(onTrackChangeListener)
|
||||
|
||||
binding.playPause.setOnClickListener {
|
||||
if (!this::playerService.isInitialized) return@setOnClickListener
|
||||
if (isPaused) playerService.play() else playerService.pause()
|
||||
}
|
||||
|
||||
// load the stream info into the UI
|
||||
updateStreamInfo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the information from a new stream into the UI
|
||||
*/
|
||||
private fun updateStreamInfo() {
|
||||
val current = PlayingQueue.getCurrent()
|
||||
current ?: return
|
||||
|
||||
binding.title.text = current.title
|
||||
binding.uploader.text = current.uploaderName
|
||||
binding.uploader.setOnClickListener {
|
||||
NavigationHelper.navigateChannel(requireContext(), current.uploaderUrl?.toID())
|
||||
}
|
||||
|
||||
ImageHelper.loadImage(current.thumbnail, binding.thumbnail)
|
||||
|
||||
initializeSeekBar()
|
||||
}
|
||||
|
||||
private fun initializeSeekBar() {
|
||||
if (!this::playerService.isInitialized) return
|
||||
|
||||
binding.timeBar.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) playerService.seekToPosition(value.toLong() * 1000)
|
||||
}
|
||||
updateSeekBar()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position, duration and text views belonging to the seek bar
|
||||
*/
|
||||
private fun updateSeekBar() {
|
||||
val duration = playerService.getDuration()?.toFloat() ?: return
|
||||
|
||||
// when the video is not loaded yet, retry in 100 ms
|
||||
if (duration <= 0) {
|
||||
handler.postDelayed(this::updateSeekBar, 100)
|
||||
return
|
||||
}
|
||||
|
||||
// get the current position from the player service
|
||||
val currentPosition = playerService.getCurrentPosition()?.toFloat() ?: 0f
|
||||
|
||||
// set the text for the indicators
|
||||
binding.duration.text = DateUtils.formatElapsedTime((duration / 1000).toLong())
|
||||
binding.currentPosition.text = DateUtils.formatElapsedTime(
|
||||
(currentPosition / 1000).toLong()
|
||||
)
|
||||
|
||||
// update the time bar current value and maximum value
|
||||
binding.timeBar.valueTo = duration / 1000
|
||||
binding.timeBar.value = minOf(
|
||||
currentPosition / 1000,
|
||||
binding.timeBar.valueTo
|
||||
)
|
||||
|
||||
handler.postDelayed(this::updateSeekBar, 200)
|
||||
}
|
||||
|
||||
private fun handleServiceConnection() {
|
||||
playerService.onIsPlayingChanged = { isPlaying ->
|
||||
binding.playPause.setIconResource(
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play
|
||||
)
|
||||
isPaused = !isPlaying
|
||||
}
|
||||
initializeSeekBar()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
// unregister all listeners and the connected [playerService]
|
||||
playerService.onIsPlayingChanged = null
|
||||
activity?.unbindService(connection)
|
||||
PlayingQueue.removeOnTrackChangedListener(onTrackChangeListener)
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
@ -350,13 +350,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
||||
BackgroundHelper.stopBackgroundPlay(requireContext())
|
||||
}
|
||||
playerBinding.closeImageButton.setOnClickListener {
|
||||
viewModel.isFullscreen.value = false
|
||||
binding.playerMotionLayout.transitionToEnd()
|
||||
val mainActivity = activity as MainActivity
|
||||
mainActivity.supportFragmentManager.beginTransaction()
|
||||
.remove(this)
|
||||
.commit()
|
||||
BackgroundHelper.stopBackgroundPlay(requireContext())
|
||||
killFragment()
|
||||
}
|
||||
playerBinding.autoPlay.visibility = View.VISIBLE
|
||||
|
||||
@ -473,6 +467,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
||||
}
|
||||
|
||||
private fun playOnBackground() {
|
||||
BackgroundHelper.stopBackgroundPlay(requireContext())
|
||||
BackgroundHelper.playOnBackground(
|
||||
requireContext(),
|
||||
videoId!!,
|
||||
@ -480,6 +475,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
||||
playlistId,
|
||||
channelId
|
||||
)
|
||||
handler.postDelayed({
|
||||
(activity as MainActivity).navController.navigate(R.id.audioPlayerFragment)
|
||||
killFragment()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
private fun setFullscreen() {
|
||||
@ -1520,6 +1519,15 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
||||
return exoPlayer.isPlaying && !backgroundModeRunning
|
||||
}
|
||||
|
||||
private fun killFragment() {
|
||||
viewModel.isFullscreen.value = false
|
||||
binding.playerMotionLayout.transitionToEnd()
|
||||
val mainActivity = activity as MainActivity
|
||||
mainActivity.supportFragmentManager.beginTransaction()
|
||||
.remove(this)
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
|
@ -20,7 +20,6 @@ import com.github.libretube.api.obj.Streams
|
||||
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
|
||||
import com.github.libretube.constants.IntentData
|
||||
import com.github.libretube.constants.PLAYER_NOTIFICATION_ID
|
||||
import com.github.libretube.constants.PreferenceKeys
|
||||
import com.github.libretube.ui.activities.MainActivity
|
||||
import com.google.android.exoplayer2.ExoPlayer
|
||||
import com.google.android.exoplayer2.Player
|
||||
@ -75,11 +74,7 @@ class NowPlayingNotification(
|
||||
// that's the only way to launch back into the previous activity (e.g. the player view
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
if (isBackgroundPlayerNotification) {
|
||||
if (PreferenceHelper.getBoolean(PreferenceKeys.NOTIFICATION_OPEN_QUEUE, true)) {
|
||||
putExtra(IntentData.openQueueOnce, true)
|
||||
} else {
|
||||
putExtra(IntentData.videoId, videoId)
|
||||
}
|
||||
putExtra(IntentData.openAudioPlayer, true)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,16 @@ import kotlinx.coroutines.launch
|
||||
object PlayingQueue {
|
||||
private val queue = mutableListOf<StreamItem>()
|
||||
private var currentStream: StreamItem? = null
|
||||
|
||||
/**
|
||||
* Listener that gets called when the user selects an item from the queue
|
||||
*/
|
||||
private var onQueueTapListener: (StreamItem) -> Unit = {}
|
||||
|
||||
/**
|
||||
* Listener that gets called when the current playing video changes
|
||||
*/
|
||||
private val onTrackChangedListeners: MutableList<(StreamItem) -> Unit> = mutableListOf()
|
||||
var repeatQueue: Boolean = false
|
||||
|
||||
fun add(vararg streamItem: StreamItem) {
|
||||
@ -58,6 +67,11 @@ object PlayingQueue {
|
||||
|
||||
fun updateCurrent(streamItem: StreamItem) {
|
||||
currentStream = streamItem
|
||||
onTrackChangedListeners.forEach {
|
||||
runCatching {
|
||||
it.invoke(streamItem)
|
||||
}
|
||||
}
|
||||
if (!contains(streamItem)) queue.add(streamItem)
|
||||
}
|
||||
|
||||
@ -77,6 +91,8 @@ object PlayingQueue {
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrent(): StreamItem? = currentStream
|
||||
|
||||
fun contains(streamItem: StreamItem) = queue.any { it.url?.toID() == streamItem.url?.toID() }
|
||||
|
||||
// only returns a copy of the queue, no write access
|
||||
@ -162,9 +178,18 @@ object PlayingQueue {
|
||||
onQueueTapListener = listener
|
||||
}
|
||||
|
||||
fun addOnTrackChangedListener(listener: (StreamItem) -> Unit) {
|
||||
onTrackChangedListeners.add(listener)
|
||||
}
|
||||
|
||||
fun removeOnTrackChangedListener(listener: (StreamItem) -> Unit) {
|
||||
onTrackChangedListeners.remove(listener)
|
||||
}
|
||||
|
||||
fun resetToDefaults() {
|
||||
repeatQueue = false
|
||||
onQueueTapListener = {}
|
||||
onTrackChangedListeners.clear()
|
||||
queue.clear()
|
||||
}
|
||||
}
|
||||
|
121
app/src/main/res/layout/fragment_audio_player.xml
Normal file
121
app/src/main/res/layout/fragment_audio_player.xml
Normal file
@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Small"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:ellipsize="marquee"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/uploader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginTop="10dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/time_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:labelBehavior="gone"
|
||||
android:layout_marginHorizontal="20dp" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/current_position"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center"
|
||||
tools:text="00:00"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center"
|
||||
tools:text="10:15"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginVertical="50dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/prev"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_prev" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/play_pause"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:icon="@drawable/ic_pause"
|
||||
app:iconSize="24dp"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Full" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/next"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -25,10 +25,4 @@
|
||||
android:title="@string/about"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_queue"
|
||||
android:title="@string/queue"
|
||||
android:visible="false"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
@ -54,4 +54,9 @@
|
||||
android:name="com.github.libretube.ui.fragments.DownloadsFragment"
|
||||
android:label="@string/downloads"
|
||||
tools:layout="@layout/fragment_downloads" />
|
||||
<fragment
|
||||
android:id="@+id/audioPlayerFragment"
|
||||
android:name="com.github.libretube.ui.fragments.AudioPlayerFragment"
|
||||
android:label="@string/audio_player"
|
||||
tools:layout="@layout/fragment_audio_player" />
|
||||
</navigation>
|
@ -430,6 +430,7 @@
|
||||
<string name="pause">Pause</string>
|
||||
<string name="alternative_pip_controls">Alternative PiP controls</string>
|
||||
<string name="alternative_pip_controls_summary">Show audio only and skip controls in PiP instead of forward and rewind</string>
|
||||
<string name="audio_player">Audio player</string>
|
||||
|
||||
<!-- Notification channel strings -->
|
||||
<string name="download_channel_name">Download Service</string>
|
||||
|
@ -89,12 +89,6 @@
|
||||
app:valueFrom="0.2"
|
||||
app:valueTo="4.0" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:icon="@drawable/ic_grid"
|
||||
android:title="@string/open_queue_from_notification"
|
||||
app:key="notification_open_queue" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user