mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 14:20:30 +05:30
commit
3cf149be97
@ -34,6 +34,7 @@ import com.github.libretube.activities.hideKeyboard
|
|||||||
import com.github.libretube.adapters.ChaptersAdapter
|
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.databinding.ExoStyledPlayerControlViewBinding
|
||||||
import com.github.libretube.databinding.FragmentPlayerBinding
|
import com.github.libretube.databinding.FragmentPlayerBinding
|
||||||
import com.github.libretube.dialogs.AddtoPlaylistDialog
|
import com.github.libretube.dialogs.AddtoPlaylistDialog
|
||||||
import com.github.libretube.dialogs.DownloadDialog
|
import com.github.libretube.dialogs.DownloadDialog
|
||||||
@ -93,6 +94,7 @@ class PlayerFragment : Fragment() {
|
|||||||
|
|
||||||
private val TAG = "PlayerFragment"
|
private val TAG = "PlayerFragment"
|
||||||
private lateinit var binding: FragmentPlayerBinding
|
private lateinit var binding: FragmentPlayerBinding
|
||||||
|
private lateinit var playerBinding: ExoStyledPlayerControlViewBinding
|
||||||
|
|
||||||
private var videoId: String? = null
|
private var videoId: String? = null
|
||||||
private var playlistId: String? = null
|
private var playlistId: String? = null
|
||||||
@ -145,6 +147,7 @@ class PlayerFragment : Fragment() {
|
|||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
binding = FragmentPlayerBinding.inflate(layoutInflater, container, false)
|
binding = FragmentPlayerBinding.inflate(layoutInflater, container, false)
|
||||||
|
playerBinding = binding.player.binding
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
@ -227,7 +230,7 @@ class PlayerFragment : Fragment() {
|
|||||||
.remove(this)
|
.remove(this)
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
binding.player.binding.closeImageButton.setOnClickListener {
|
playerBinding.closeImageButton.setOnClickListener {
|
||||||
isMiniPlayerVisible = false
|
isMiniPlayerVisible = false
|
||||||
binding.playerMotionLayout.transitionToEnd()
|
binding.playerMotionLayout.transitionToEnd()
|
||||||
val mainActivity = activity as MainActivity
|
val mainActivity = activity as MainActivity
|
||||||
@ -259,11 +262,8 @@ class PlayerFragment : Fragment() {
|
|||||||
toggleComments()
|
toggleComments()
|
||||||
}
|
}
|
||||||
|
|
||||||
val fullScreenButton = binding.player.binding.fullscreen
|
|
||||||
val exoTitle = binding.player.binding.exoTitle
|
|
||||||
|
|
||||||
// FullScreen button trigger
|
// FullScreen button trigger
|
||||||
fullScreenButton.setOnClickListener {
|
playerBinding.fullscreen.setOnClickListener {
|
||||||
exoPlayerView.hideController()
|
exoPlayerView.hideController()
|
||||||
if (!isFullScreen) {
|
if (!isFullScreen) {
|
||||||
with(binding.playerMotionLayout) {
|
with(binding.playerMotionLayout) {
|
||||||
@ -273,8 +273,8 @@ class PlayerFragment : Fragment() {
|
|||||||
|
|
||||||
binding.mainContainer.isClickable = true
|
binding.mainContainer.isClickable = true
|
||||||
binding.linLayout.visibility = View.GONE
|
binding.linLayout.visibility = View.GONE
|
||||||
fullScreenButton.setImageResource(R.drawable.ic_fullscreen_exit)
|
playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen_exit)
|
||||||
exoTitle.visibility = View.VISIBLE
|
playerBinding.exoTitle.visibility = View.VISIBLE
|
||||||
|
|
||||||
val mainActivity = activity as MainActivity
|
val mainActivity = activity as MainActivity
|
||||||
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||||
@ -286,8 +286,8 @@ class PlayerFragment : Fragment() {
|
|||||||
|
|
||||||
binding.mainContainer.isClickable = false
|
binding.mainContainer.isClickable = false
|
||||||
binding.linLayout.visibility = View.VISIBLE
|
binding.linLayout.visibility = View.VISIBLE
|
||||||
fullScreenButton.setImageResource(R.drawable.ic_fullscreen)
|
playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen)
|
||||||
exoTitle.visibility = View.INVISIBLE
|
playerBinding.exoTitle.visibility = View.INVISIBLE
|
||||||
|
|
||||||
val mainActivity = activity as MainActivity
|
val mainActivity = activity as MainActivity
|
||||||
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
||||||
@ -296,8 +296,7 @@ class PlayerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// switching between original aspect ratio (black bars) and zoomed to fill device screen
|
// switching between original aspect ratio (black bars) and zoomed to fill device screen
|
||||||
val aspectRatioButton = binding.player.binding.aspectRatioButton
|
playerBinding.aspectRatioButton.setOnClickListener {
|
||||||
aspectRatioButton.setOnClickListener {
|
|
||||||
if (isZoomed) {
|
if (isZoomed) {
|
||||||
exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
|
exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
isZoomed = false
|
isZoomed = false
|
||||||
@ -308,13 +307,12 @@ class PlayerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lock and unlock the player
|
// lock and unlock the player
|
||||||
val lockPlayerButton = binding.player.binding.lockPlayer
|
playerBinding.lockPlayer.setOnClickListener {
|
||||||
lockPlayerButton.setOnClickListener {
|
|
||||||
// change the locked/unlocked icon
|
// change the locked/unlocked icon
|
||||||
if (!isPlayerLocked) {
|
if (!isPlayerLocked) {
|
||||||
lockPlayerButton.setImageResource(R.drawable.ic_locked)
|
playerBinding.lockPlayer.setImageResource(R.drawable.ic_locked)
|
||||||
} else {
|
} else {
|
||||||
lockPlayerButton.setImageResource(R.drawable.ic_unlocked)
|
playerBinding.lockPlayer.setImageResource(R.drawable.ic_unlocked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// show/hide all the controls
|
// show/hide all the controls
|
||||||
@ -373,6 +371,7 @@ class PlayerFragment : Fragment() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
try {
|
try {
|
||||||
|
saveWatchPosition()
|
||||||
mediaSession.isActive = false
|
mediaSession.isActive = false
|
||||||
mediaSession.release()
|
mediaSession.release()
|
||||||
mediaSessionConnector.setPlayer(null)
|
mediaSessionConnector.setPlayer(null)
|
||||||
@ -386,6 +385,25 @@ class PlayerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save the watch position if video isn't finished and option enabled
|
||||||
|
private fun saveWatchPosition() {
|
||||||
|
val watchPositionsEnabled = PreferenceHelper.getBoolean(
|
||||||
|
requireContext(),
|
||||||
|
"watch_positions_toggle",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
if (watchPositionsEnabled && exoPlayer.currentPosition != exoPlayer.duration) {
|
||||||
|
PreferenceHelper.saveWatchPosition(
|
||||||
|
requireContext(),
|
||||||
|
videoId!!,
|
||||||
|
exoPlayer.currentPosition
|
||||||
|
)
|
||||||
|
} else if (watchPositionsEnabled) {
|
||||||
|
// delete watch position if video has ended
|
||||||
|
PreferenceHelper.removeWatchPosition(requireContext(), videoId!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkForSegments() {
|
private fun checkForSegments() {
|
||||||
if (!exoPlayer.isPlaying || !sponsorBlockPrefs.sponsorBlockEnabled) return
|
if (!exoPlayer.isPlaying || !sponsorBlockPrefs.sponsorBlockEnabled) return
|
||||||
|
|
||||||
@ -447,6 +465,7 @@ class PlayerFragment : Fragment() {
|
|||||||
val position = arguments?.getLong("timeStamp")!! * 1000
|
val position = arguments?.getLong("timeStamp")!! * 1000
|
||||||
exoPlayer.seekTo(position)
|
exoPlayer.seekTo(position)
|
||||||
}
|
}
|
||||||
|
seekToWatchPosition()
|
||||||
exoPlayer.play()
|
exoPlayer.play()
|
||||||
exoPlayerView.useController = true
|
exoPlayerView.useController = true
|
||||||
initializePlayerNotification(requireContext())
|
initializePlayerNotification(requireContext())
|
||||||
@ -466,6 +485,14 @@ class PlayerFragment : Fragment() {
|
|||||||
run()
|
run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// seek to saved watch position if available
|
||||||
|
private fun seekToWatchPosition() {
|
||||||
|
val watchPositions = PreferenceHelper.getWatchPositions(requireContext())
|
||||||
|
watchPositions.forEach {
|
||||||
|
if (it.videoId == videoId) exoPlayer.seekTo(it.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// the function is working recursively
|
// the function is working recursively
|
||||||
private fun initAutoPlay() {
|
private fun initAutoPlay() {
|
||||||
// save related streams for autoplay
|
// save related streams for autoplay
|
||||||
@ -629,7 +656,7 @@ class PlayerFragment : Fragment() {
|
|||||||
binding.playerTitle.text = response.title
|
binding.playerTitle.text = response.title
|
||||||
binding.playerDescription.text = response.description
|
binding.playerDescription.text = response.description
|
||||||
|
|
||||||
binding.player.binding.exoTitle.text = response.title
|
playerBinding.exoTitle.text = response.title
|
||||||
|
|
||||||
// Listener for play and pause icon change
|
// Listener for play and pause icon change
|
||||||
exoPlayer.addListener(object : Player.Listener {
|
exoPlayer.addListener(object : Player.Listener {
|
||||||
@ -801,9 +828,6 @@ class PlayerFragment : Fragment() {
|
|||||||
PreferenceHelper.getString(requireContext(), "player_video_format", "WEBM")
|
PreferenceHelper.getString(requireContext(), "player_video_format", "WEBM")
|
||||||
val defres = PreferenceHelper.getString(requireContext(), "default_res", "")!!
|
val defres = PreferenceHelper.getString(requireContext(), "default_res", "")!!
|
||||||
|
|
||||||
val qualityText = binding.player.binding.qualityText
|
|
||||||
val qualitySelect = binding.player.binding.qualitySelect
|
|
||||||
|
|
||||||
var videosNameArray: Array<CharSequence> = arrayOf()
|
var videosNameArray: Array<CharSequence> = arrayOf()
|
||||||
var videosUrlArray: Array<Uri> = arrayOf()
|
var videosUrlArray: Array<Uri> = arrayOf()
|
||||||
|
|
||||||
@ -843,7 +867,7 @@ class PlayerFragment : Fragment() {
|
|||||||
val videoUri = videosUrlArray[index]
|
val videoUri = videosUrlArray[index]
|
||||||
val audioUrl = getMostBitRate(response.audioStreams!!)
|
val audioUrl = getMostBitRate(response.audioStreams!!)
|
||||||
setMediaSource(subtitle, videoUri, audioUrl)
|
setMediaSource(subtitle, videoUri, audioUrl)
|
||||||
qualityText.text = videosNameArray[index]
|
playerBinding.qualityText.text = videosNameArray[index]
|
||||||
return@lit
|
return@lit
|
||||||
} else if (response.hls != null) {
|
} else if (response.hls != null) {
|
||||||
val mediaItem: MediaItem = MediaItem.Builder()
|
val mediaItem: MediaItem = MediaItem.Builder()
|
||||||
@ -874,11 +898,11 @@ class PlayerFragment : Fragment() {
|
|||||||
val videoUri = videosUrlArray[0]
|
val videoUri = videosUrlArray[0]
|
||||||
val audioUrl = getMostBitRate(response.audioStreams!!)
|
val audioUrl = getMostBitRate(response.audioStreams!!)
|
||||||
setMediaSource(subtitle, videoUri, audioUrl)
|
setMediaSource(subtitle, videoUri, audioUrl)
|
||||||
qualityText.text = videosNameArray[0]
|
playerBinding.qualityText.text = videosNameArray[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qualitySelect.setOnClickListener {
|
playerBinding.qualitySelect.setOnClickListener {
|
||||||
// Dialog for quality selection
|
// Dialog for quality selection
|
||||||
val builder: MaterialAlertDialogBuilder? = activity?.let {
|
val builder: MaterialAlertDialogBuilder? = activity?.let {
|
||||||
MaterialAlertDialogBuilder(it)
|
MaterialAlertDialogBuilder(it)
|
||||||
@ -905,7 +929,7 @@ class PlayerFragment : Fragment() {
|
|||||||
setMediaSource(subtitle, videoUri, audioUrl)
|
setMediaSource(subtitle, videoUri, audioUrl)
|
||||||
}
|
}
|
||||||
exoPlayer.seekTo(lastPosition)
|
exoPlayer.seekTo(lastPosition)
|
||||||
qualityText.text = videosNameArray[which]
|
playerBinding.qualityText.text = videosNameArray[which]
|
||||||
}
|
}
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
@ -986,12 +1010,12 @@ class PlayerFragment : Fragment() {
|
|||||||
|
|
||||||
private fun lockPlayer(isLocked: Boolean) {
|
private fun lockPlayer(isLocked: Boolean) {
|
||||||
val visibility = if (isLocked) View.VISIBLE else View.INVISIBLE
|
val visibility = if (isLocked) View.VISIBLE else View.INVISIBLE
|
||||||
binding.player.binding.exoTopBarRight.visibility = visibility
|
playerBinding.exoTopBarRight.visibility = visibility
|
||||||
binding.player.binding.exoPlayPause.visibility = visibility
|
playerBinding.exoPlayPause.visibility = visibility
|
||||||
binding.player.binding.exoFfwdWithAmount.visibility = visibility
|
playerBinding.exoFfwdWithAmount.visibility = visibility
|
||||||
binding.player.binding.exoRewWithAmount.visibility = visibility
|
playerBinding.exoRewWithAmount.visibility = visibility
|
||||||
binding.player.binding.exoBottomBar.visibility = visibility
|
playerBinding.exoBottomBar.visibility = visibility
|
||||||
binding.player.binding.exoTitle.visibility =
|
playerBinding.exoTitle.visibility =
|
||||||
if (isLocked && isFullScreen) View.VISIBLE else View.INVISIBLE
|
if (isLocked && isFullScreen) View.VISIBLE else View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1153,7 +1177,7 @@ class PlayerFragment : Fragment() {
|
|||||||
enableTransition(R.id.yt_transition, false)
|
enableTransition(R.id.yt_transition, false)
|
||||||
}
|
}
|
||||||
binding.mainContainer.isClickable = true
|
binding.mainContainer.isClickable = true
|
||||||
binding.player.binding.exoTopBar.visibility = View.GONE
|
playerBinding.exoTopBar.visibility = View.GONE
|
||||||
val mainActivity = activity as MainActivity
|
val mainActivity = activity as MainActivity
|
||||||
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
||||||
isFullScreen = false
|
isFullScreen = false
|
||||||
@ -1165,7 +1189,7 @@ class PlayerFragment : Fragment() {
|
|||||||
exoPlayerView.showController()
|
exoPlayerView.showController()
|
||||||
exoPlayerView.useController = true
|
exoPlayerView.useController = true
|
||||||
binding.mainContainer.isClickable = false
|
binding.mainContainer.isClickable = false
|
||||||
binding.player.binding.exoTopBar.visibility = View.VISIBLE
|
playerBinding.exoTopBar.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.github.libretube.obj
|
||||||
|
|
||||||
|
data class WatchPosition(
|
||||||
|
val videoId: String,
|
||||||
|
val position: Long
|
||||||
|
)
|
@ -25,10 +25,11 @@ class AdvancedSettings : PreferenceFragmentCompat() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear watch history
|
// clear watch history and positions
|
||||||
val clearWatchHistory = findPreference<Preference>("clear_watch_history")
|
val clearWatchHistory = findPreference<Preference>("clear_watch_history")
|
||||||
clearWatchHistory?.setOnPreferenceClickListener {
|
clearWatchHistory?.setOnPreferenceClickListener {
|
||||||
PreferenceHelper.removePreference(requireContext(), "watch_history")
|
PreferenceHelper.removePreference(requireContext(), "watch_history")
|
||||||
|
PreferenceHelper.removePreference(requireContext(), "watch_positions")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import androidx.preference.PreferenceManager
|
|||||||
import com.github.libretube.obj.CustomInstance
|
import com.github.libretube.obj.CustomInstance
|
||||||
import com.github.libretube.obj.Streams
|
import com.github.libretube.obj.Streams
|
||||||
import com.github.libretube.obj.WatchHistoryItem
|
import com.github.libretube.obj.WatchHistoryItem
|
||||||
|
import com.github.libretube.obj.WatchPosition
|
||||||
import com.google.common.reflect.TypeToken
|
import com.google.common.reflect.TypeToken
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
@ -144,11 +145,11 @@ object PreferenceHelper {
|
|||||||
val watchHistory = getWatchHistory(context)
|
val watchHistory = getWatchHistory(context)
|
||||||
|
|
||||||
// delete entries that have the same videoId
|
// delete entries that have the same videoId
|
||||||
var indexToRemove = Int.MAX_VALUE
|
var indexToRemove: Int? = null
|
||||||
watchHistory.forEachIndexed { index, item ->
|
watchHistory.forEachIndexed { index, item ->
|
||||||
if (item.videoId == videoId) indexToRemove = index
|
if (item.videoId == videoId) indexToRemove = index
|
||||||
}
|
}
|
||||||
if (indexToRemove != Int.MAX_VALUE) watchHistory.removeAt(indexToRemove)
|
if (indexToRemove != null) watchHistory.removeAt(indexToRemove!!)
|
||||||
|
|
||||||
watchHistory += watchHistoryItem
|
watchHistory += watchHistoryItem
|
||||||
|
|
||||||
@ -168,6 +169,55 @@ object PreferenceHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveWatchPosition(context: Context, videoId: String, position: Long) {
|
||||||
|
val editor = getDefaultSharedPreferencesEditor(context)
|
||||||
|
|
||||||
|
val watchPositions = getWatchPositions(context)
|
||||||
|
val watchPositionItem = WatchPosition(videoId, position)
|
||||||
|
|
||||||
|
var indexToRemove: Int? = null
|
||||||
|
watchPositions.forEachIndexed { index, item ->
|
||||||
|
if (item.videoId == videoId) indexToRemove = index
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexToRemove != null) watchPositions.removeAt(indexToRemove!!)
|
||||||
|
|
||||||
|
watchPositions += watchPositionItem
|
||||||
|
|
||||||
|
val gson = Gson()
|
||||||
|
val json = gson.toJson(watchPositions)
|
||||||
|
editor.putString("watch_positions", json).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeWatchPosition(context: Context, videoId: String) {
|
||||||
|
val editor = getDefaultSharedPreferencesEditor(context)
|
||||||
|
|
||||||
|
val watchPositions = getWatchPositions(context)
|
||||||
|
|
||||||
|
var indexToRemove: Int? = null
|
||||||
|
watchPositions.forEachIndexed { index, item ->
|
||||||
|
if (item.videoId == videoId) indexToRemove = index
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexToRemove != null) watchPositions.removeAt(indexToRemove!!)
|
||||||
|
|
||||||
|
val gson = Gson()
|
||||||
|
val json = gson.toJson(watchPositions)
|
||||||
|
editor.putString("watch_positions", json).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getWatchPositions(context: Context): ArrayList<WatchPosition> {
|
||||||
|
val settings = getDefaultSharedPreferences(context)
|
||||||
|
val gson = Gson()
|
||||||
|
val json: String = settings.getString("watch_positions", "")!!
|
||||||
|
val type: Type = object : TypeToken<List<WatchPosition?>?>() {}.type
|
||||||
|
return try {
|
||||||
|
gson.fromJson(json, type)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
arrayListOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
|
private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
}
|
}
|
||||||
|
@ -208,4 +208,6 @@
|
|||||||
<string name="account">Account</string>
|
<string name="account">Account</string>
|
||||||
<string name="restore">Restore</string>
|
<string name="restore">Restore</string>
|
||||||
<string name="watch_history">Watch History</string>
|
<string name="watch_history">Watch History</string>
|
||||||
|
<string name="watch_positions">Remember position</string>
|
||||||
|
<string name="watch_positions_summary">Remember the watch position and automatically seek to it.</string>
|
||||||
</resources>
|
</resources>
|
@ -54,6 +54,13 @@
|
|||||||
app:key="watch_history_toggle"
|
app:key="watch_history_toggle"
|
||||||
app:title="@string/watch_history" />
|
app:title="@string/watch_history" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:icon="@drawable/ic_play_filled"
|
||||||
|
app:key="watch_position_toggle"
|
||||||
|
app:title="@string/watch_positions"
|
||||||
|
app:summary="@string/watch_positions_summary"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_trash"
|
android:icon="@drawable/ic_trash"
|
||||||
app:key="clear_watch_history"
|
app:key="clear_watch_history"
|
||||||
|
Loading…
Reference in New Issue
Block a user