Merge branch 'libre-tube:master' into master

This commit is contained in:
Faisal Khan 2023-01-17 23:50:33 +05:30 committed by GitHub
commit b805e0e375
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1282 additions and 516 deletions

View File

@ -47,6 +47,21 @@
android:name=".ui.activities.CommunityActivity"
android:label="@string/settings" />
<activity
android:name=".ui.activities.AddToQueueActivity"
android:enabled="true"
android:launchMode="singleTop"
android:exported="true"
android:label="@string/add_to_queue">
<intent-filter android:label="@string/add_to_queue">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".ui.activities.OfflinePlayerActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
@ -67,9 +82,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity-alias
@ -89,9 +101,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity-alias
@ -111,9 +120,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity-alias
@ -133,9 +139,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity-alias
@ -155,9 +158,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity-alias
@ -175,9 +175,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity-alias
@ -197,9 +194,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity-alias
@ -219,9 +213,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity-alias
@ -241,9 +232,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity
@ -251,7 +239,7 @@
android:exported="true"
android:launchMode="singleInstance">
<intent-filter>
<intent-filter android:label="@string/open">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />

View File

@ -17,6 +17,7 @@ import com.github.libretube.util.ImageHelper
import com.github.libretube.util.NotificationHelper
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.ProxyHelper
import com.github.libretube.util.ShortcutHelper
class LibreTubeApp : Application() {
override fun onCreate() {
@ -69,6 +70,11 @@ class LibreTubeApp : Application() {
val defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
val exceptionHandler = ExceptionHandler(defaultExceptionHandler)
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
/**
* Dynamically create App Shortcuts
*/
ShortcutHelper.createShortcuts(this)
}
/**

View File

@ -17,4 +17,5 @@ object IntentData {
const val subtitleCode = "subtitleCode"
const val downloading = "downloading"
const val openAudioPlayer = "openAudioPlayer"
const val fragmentToOpen = "fragmentToOpen"
}

View File

@ -18,8 +18,8 @@ object PreferenceKeys {
const val LANGUAGE = "language"
const val REGION = "region"
const val AUTO_ROTATION = "auto_rotation"
const val BREAK_REMINDER_TOGGLE = "break_reminder_toggle"
const val BREAK_REMINDER = "break_reminder"
const val SLEEP_TIMER = "sleep_timer_toggle"
const val SLEEP_TIMER_DELAY = "sleep_timer_delay"
const val SAVE_FEED = "save_feed"
const val NAVBAR_ITEMS = "navbar_items"
const val START_FRAGMENT = "start_fragment"
@ -88,6 +88,7 @@ object PreferenceKeys {
const val DOUBLE_TAP_TO_SEEK = "double_tap_seek"
const val PAUSE_ON_QUIT = "pause_on_quit"
const val ALTERNATIVE_PIP_CONTROLS = "alternative_pip_controls"
const val SKIP_SILENCE = "skip_silence"
/**
* Background mode

View File

@ -0,0 +1,10 @@
package com.github.libretube.obj
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
data class AppShortcut(
val action: String,
@StringRes val label: Int,
@DrawableRes val drawable: Int
)

View File

@ -169,6 +169,9 @@ class BackgroundMode : Service() {
/**
* Gets the video data and prepares the [player].
* @param videoId The id of the video to play
* @param seekToPosition The position of the video to seek to
* @param keepQueue Whether to keep the queue or clear it instead
*/
private fun loadAudio(
videoId: String,
@ -298,7 +301,7 @@ class BackgroundMode : Service() {
this.videoId = nextVideo
this.streams = null
this.segmentData = null
loadAudio(videoId)
loadAudio(videoId, keepQueue = true)
}
/**

View File

@ -0,0 +1,39 @@
package com.github.libretube.ui.activities
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.util.PlayingQueue
/**
* Receives a text by the intent and attempts to add it to the playing queue
* If no video is playing currently, the queue will be left unchanged and the the main activity is being resumed
*/
class AddToQueueActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uri = Uri.parse(intent.getStringExtra(Intent.EXTRA_TEXT)!!)
var videoId: String? = null
listOf("/shorts/", "/v/", "/embed/").forEach {
if (uri.path!!.contains(it)) {
videoId = uri.path!!.replace(it, "")
}
}
if (
uri.path!!.contains("/watch") && uri.query != null
) {
videoId = uri.getQueryParameter("v")
}
if (videoId == null) videoId = uri.path!!.replace("/", "")
// if playing a video currently, the playing queue is not empty
if (PlayingQueue.isNotEmpty()) PlayingQueue.insertByVideoId(videoId!!)
val intent = packageManager.getLaunchIntentForPackage(packageName)
startActivity(intent)
finishAndRemoveTask()
}
}

View File

@ -6,15 +6,10 @@ import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.WindowManager
import android.widget.ScrollView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SearchView
@ -40,12 +35,13 @@ 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.tools.BreakReminder
import com.github.libretube.ui.tools.SleepTimer
import com.github.libretube.util.NavBarHelper
import com.github.libretube.util.NavigationHelper
import com.github.libretube.util.NetworkHelper
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.ThemeHelper
import com.github.libretube.util.WindowHelper
import com.google.android.material.elevation.SurfaceColors
class MainActivity : BaseActivity() {
@ -60,7 +56,7 @@ class MainActivity : BaseActivity() {
lateinit var searchView: SearchView
private lateinit var searchItem: MenuItem
private val handler = Handler(Looper.getMainLooper())
val windowHelper = WindowHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -136,13 +132,12 @@ class MainActivity : BaseActivity() {
binding.toolbar.title = ThemeHelper.getStyledAppName(this)
/**
* handle error logs
*/
val log = PreferenceHelper.getErrorLog()
if (log != "") ErrorDialog().show(supportFragmentManager, null)
// handle error logs
PreferenceHelper.getErrorLog().ifBlank { null }?.let {
ErrorDialog().show(supportFragmentManager, null)
}
BreakReminder.setupBreakReminder(applicationContext)
SleepTimer.setup(this)
setupSubscriptionsBadge()
@ -161,11 +156,9 @@ class MainActivity : BaseActivity() {
}
if (binding.mainMotionLayout.progress == 0F) {
try {
runCatching {
minimizePlayer()
return
} catch (e: Exception) {
// current fragment isn't the player fragment
}
}
@ -247,6 +240,7 @@ class MainActivity : BaseActivity() {
binding.bottomNav.getOrCreateBadge(R.id.subscriptionsFragment).apply {
number = lastSeenVideoIndex
backgroundColor = ThemeHelper.getThemeColor(this@MainActivity, R.attr.colorPrimary)
badgeTextColor = ThemeHelper.getThemeColor(this@MainActivity, R.attr.colorOnPrimary)
}
}
}
@ -467,68 +461,8 @@ class MainActivity : BaseActivity() {
super.onConfigurationChanged(newConfig)
when (newConfig.orientation) {
Configuration.ORIENTATION_PORTRAIT -> unsetFullscreen()
Configuration.ORIENTATION_LANDSCAPE -> setFullscreen()
}
}
fun setFullscreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false)
window.insetsController?.apply {
hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
} else {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_IMMERSIVE
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
)
}
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
}
private fun unsetFullscreen() {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
@Suppress("DEPRECATION")
window.clearFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(true)
window.insetsController?.apply {
show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
systemBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT
}
}
} else {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_VISIBLE or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
Configuration.ORIENTATION_PORTRAIT -> windowHelper.unsetFullscreen()
Configuration.ORIENTATION_LANDSCAPE -> windowHelper.setFullscreen()
}
}

View File

@ -2,18 +2,13 @@ package com.github.libretube.ui.activities
import android.app.PictureInPictureParams
import android.content.pm.ActivityInfo
import android.graphics.Color
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.format.DateUtils
import android.view.View
import android.view.WindowManager
import androidx.activity.viewModels
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.ActivityOfflinePlayerBinding
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
@ -25,6 +20,7 @@ import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.extensions.setAspectRatio
import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.util.PlayerHelper
import com.github.libretube.util.WindowHelper
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
@ -49,7 +45,7 @@ class OfflinePlayerActivity : BaseActivity() {
private val playerViewModel: PlayerViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
hideSystemBars()
WindowHelper(this).setFullscreen()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
@ -179,32 +175,6 @@ class OfflinePlayerActivity : BaseActivity() {
}
}
@Suppress("DEPRECATION")
private fun hideSystemBars() {
window?.decorView?.systemUiVisibility = (
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
)
window.statusBarColor = Color.TRANSPARENT
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
)
val windowInsetsController =
WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.statusBars())
supportActionBar?.hide()
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
}
override fun onResume() {
playerViewModel.isFullscreen.value = true
super.onResume()

View File

@ -84,10 +84,8 @@ class RouterActivity : BaseActivity() {
val pm: PackageManager = this.packageManager
val intent = pm.getLaunchIntentForPackage(this.packageName)
intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
this.startActivity(
resolveType(intent!!, uri)
)
this.finishAndRemoveTask()
startActivity(resolveType(intent!!, uri))
finishAndRemoveTask()
}
private fun parseTimestamp(t: String): Long? {

View File

@ -29,6 +29,7 @@ import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.net.toUri
import androidx.core.os.ConfigurationCompat
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
@ -252,8 +253,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
super.onViewCreated(view, savedInstanceState)
context?.hideKeyboard(view)
// reset the callbacks of the playing queue
PlayingQueue.resetToDefaults()
// clear the playing queue
if (!keepQueue) PlayingQueue.resetToDefaults()
if (!keepQueue) PlayingQueue.clear()
changeOrientationMode()
@ -342,15 +346,13 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
// actions that don't depend on video information
private fun initializeOnClickActions() {
binding.closeImageView.setOnClickListener {
viewModel.isMiniPlayerVisible.value = false
binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity
mainActivity.supportFragmentManager.beginTransaction()
.remove(this)
.commit()
PlayingQueue.clear()
BackgroundHelper.stopBackgroundPlay(requireContext())
killPlayerFragment()
}
playerBinding.closeImageButton.setOnClickListener {
PlayingQueue.clear()
BackgroundHelper.stopBackgroundPlay(requireContext())
killPlayerFragment()
}
playerBinding.autoPlay.visibility = View.VISIBLE
@ -832,11 +834,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
}
private fun localizedDate(date: String?): String? {
return if (SDK_INT >= Build.VERSION_CODES.N) {
TextUtils.localizeDate(date, resources.configuration.locales[0])
} else {
TextUtils.localizeDate(date)
}
val locale = ConfigurationCompat.getLocales(resources.configuration)[0]!!
return TextUtils.localizeDate(date, locale)
}
private fun handleLiveVideo() {
@ -1319,8 +1318,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
}.flatten()
.filter { it > 0 }
.sortedDescending()
.toSet()
.toList()
.distinct()
return resolutions.map {
VideoResolution(
@ -1469,8 +1467,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
override fun onCaptionsClicked() {
if (!this@PlayerFragment::streams.isInitialized ||
streams.subtitles == null ||
streams.subtitles!!.isEmpty()
streams.subtitles.isNullOrEmpty()
) {
Toast.makeText(context, R.string.no_subtitles_available, Toast.LENGTH_SHORT).show()
return
@ -1485,7 +1482,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
BaseBottomSheet()
.setSimpleItems(subtitlesNamesList) { index ->
val language = if (index > 0) subtitleCodesList[index] else null
val language = subtitleCodesList.getOrNull(index)
updateCaptionsLanguage(language)
this.captionLanguage = language
}
@ -1634,6 +1631,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
mainActivity.supportFragmentManager.beginTransaction()
.remove(this)
.commit()
onDestroy()
}
override fun onConfigurationChanged(newConfig: Configuration) {

View File

@ -30,21 +30,26 @@ class GeneralSettings : BasePreferenceFragment() {
val autoRotation = findPreference<SwitchPreferenceCompat>(PreferenceKeys.AUTO_ROTATION)
autoRotation?.setOnPreferenceChangeListener { _, _ ->
val restartDialog = RequireRestartDialog()
restartDialog.show(childFragmentManager, RequireRestartDialog::class.java.name)
RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name)
true
}
val breakReminder =
findPreference<SwitchPreferenceCompat>(PreferenceKeys.BREAK_REMINDER_TOGGLE)
val breakReminderTime = findPreference<EditTextPreference>(PreferenceKeys.BREAK_REMINDER)
findPreference<SwitchPreferenceCompat>(PreferenceKeys.SLEEP_TIMER)
val breakReminderTime = findPreference<EditTextPreference>(PreferenceKeys.SLEEP_TIMER_DELAY)
breakReminderTime?.isEnabled = PreferenceHelper.getBoolean(
PreferenceKeys.BREAK_REMINDER_TOGGLE,
PreferenceKeys.SLEEP_TIMER,
false
)
breakReminder?.setOnPreferenceChangeListener { _, newValue ->
breakReminderTime?.isEnabled = newValue as Boolean
RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name)
true
}
breakReminderTime?.setOnPreferenceChangeListener { _, _ ->
RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name)
true
}
}

View File

@ -73,7 +73,7 @@ class PlayerSettings : BasePreferenceFragment() {
}
private fun setupSubtitlePref(preference: ListPreference) {
val locales = LocaleHelper.getAvailableLocales().sortedBy { it.name }
val locales = LocaleHelper.getAvailableLocales()
val localeNames = locales.map { it.name }
.toMutableList()
localeNames.add(0, requireContext().getString(R.string.none))

View File

@ -4,13 +4,15 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.PlaybackBottomSheetBinding
import com.github.libretube.extensions.round
import com.github.libretube.util.PreferenceHelper
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.Player
class PlaybackSpeedSheet(
private val player: Player
private val player: ExoPlayer
) : ExpandedBottomSheet() {
private lateinit var binding: PlaybackBottomSheetBinding
@ -28,6 +30,9 @@ class PlaybackSpeedSheet(
binding.speed.value = player.playbackParameters.speed
binding.pitch.value = player.playbackParameters.pitch
PreferenceHelper.getBoolean(PreferenceKeys.SKIP_SILENCE, false).let {
binding.skipSilence.isChecked = it
}
binding.speed.addOnChangeListener { _, _, _ ->
onChange()
@ -46,6 +51,11 @@ class PlaybackSpeedSheet(
binding.pitch.value = 1f
onChange()
}
binding.skipSilence.setOnCheckedChangeListener { _, isChecked ->
player.skipSilenceEnabled = isChecked
PreferenceHelper.putBoolean(PreferenceKeys.SKIP_SILENCE, isChecked)
}
}
private fun onChange() {

View File

@ -1,55 +0,0 @@
package com.github.libretube.ui.tools
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import com.github.libretube.R
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.util.PreferenceHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
object BreakReminder {
/**
* Show a break reminder when watched too long
*/
fun setupBreakReminder(context: Context) {
if (!PreferenceHelper.getBoolean(
PreferenceKeys.BREAK_REMINDER_TOGGLE,
false
)
) {
return
}
val breakReminderPref = PreferenceHelper.getString(
PreferenceKeys.BREAK_REMINDER,
"0"
)
if (!breakReminderPref.all { Character.isDigit(it) } ||
breakReminderPref == "" || breakReminderPref == "0"
) {
return
}
Handler(Looper.getMainLooper()).postDelayed(
{
try {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.take_a_break)
.setMessage(
context.getString(
R.string.already_spent_time,
breakReminderPref
)
)
.setPositiveButton(R.string.okay, null)
.show()
} catch (e: Exception) {
runCatching {
Toast.makeText(context, R.string.take_a_break, Toast.LENGTH_LONG).show()
}
}
},
breakReminderPref.toLong() * 60 * 1000
)
}
}

View File

@ -0,0 +1,62 @@
package com.github.libretube.ui.tools
import android.content.Context
import android.os.Handler
import android.os.Looper
import com.github.libretube.R
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.util.PreferenceHelper
import com.google.android.material.snackbar.Snackbar
object SleepTimer {
private val handler = Handler(Looper.getMainLooper())
private const val REACTION_INTERVAL = 5L
/**
* Kill the app after showing a warning after a certain amount of time
* @param context This must not be the applicationContext!
*/
fun setup(context: Context) {
if (!PreferenceHelper.getBoolean(PreferenceKeys.SLEEP_TIMER, false)) return
val breakReminderPref = PreferenceHelper.getString(
PreferenceKeys.SLEEP_TIMER_DELAY,
""
).ifEmpty { return }
handler.postDelayed(
{
var killApp = true
val mainActivity = context as? MainActivity ?: return@postDelayed
val snackBar = Snackbar.make(
mainActivity.binding.root,
R.string.take_a_break,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.cancel) {
killApp = false
}
snackBar.show()
(0..REACTION_INTERVAL).forEach {
handler.postDelayed({
val remainingTime = " (${REACTION_INTERVAL - it})"
snackBar.setText(context.getString(R.string.take_a_break) + remainingTime)
}, it * 1000)
}
handler.postDelayed(
killApp@{
if (!killApp) return@killApp
// kill the application
mainActivity.finishAffinity()
mainActivity.finish()
android.os.Process.killProcess(android.os.Process.myPid())
},
REACTION_INTERVAL * 1000
)
},
breakReminderPref.toLong() * 60 * 1000
)
}
}

View File

@ -15,6 +15,7 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.github.libretube.R
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.DoubleTapOverlayBinding
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.github.libretube.databinding.PlayerGestureControlsViewBinding
@ -33,6 +34,8 @@ import com.github.libretube.util.BrightnessHelper
import com.github.libretube.util.PlayerGestureController
import com.github.libretube.util.PlayerHelper
import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.PreferenceHelper
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.trackselection.TrackSelector
@ -111,6 +114,9 @@ internal class CustomExoPlayerView(
PlayerHelper.playbackSpeed.toFloat(),
1.0f
)
PreferenceHelper.getBoolean(PreferenceKeys.SKIP_SILENCE, false).let {
(player as ExoPlayer).skipSilenceEnabled = true
}
playbackPrefSet = true
}
@ -189,7 +195,7 @@ internal class CustomExoPlayerView(
override fun hideController() {
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// hide all the navigation bars that potentially could have been reopened manually ba the user
(context as? MainActivity)?.setFullscreen()
(context as? MainActivity)?.windowHelper?.setFullscreen()
}
super.hideController()
}
@ -484,7 +490,9 @@ internal class CustomExoPlayerView(
}
override fun onPlaybackSpeedClicked() {
player?.let { PlaybackSpeedSheet(it).show(supportFragmentManager) }
player?.let {
PlaybackSpeedSheet(it as ExoPlayer).show(supportFragmentManager)
}
}
override fun onResizeModeClicked() {

View File

@ -6,60 +6,39 @@ import android.util.AttributeSet
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.TextUtils
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import java.time.LocalTime
class TimePickerPreference(
context: Context,
attributeSet: AttributeSet
) : Preference(context, attributeSet) {
override fun getSummary(): CharSequence {
val prefStr = PreferenceHelper.getString(key, "")
return if (prefStr != "") prefStr else DEFAULT_VALUE
return PreferenceHelper.getString(key, DEFAULT_VALUE)
}
override fun onClick() {
val prefTime = LocalTime.parse(PreferenceHelper.getString(key, DEFAULT_VALUE))
val picker = MaterialTimePicker.Builder()
.setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK)
.setTimeFormat(getTimeFormat())
.setHour(getHour())
.setMinute(getMinutes())
.setTimeFormat(timeFormat)
.setHour(prefTime.hour)
.setMinute(prefTime.minute)
.build()
picker.addOnPositiveButtonClickListener {
val timeStr = getTimeStr(picker)
val timeStr = LocalTime.of(picker.hour, picker.minute).toString()
PreferenceHelper.putString(key, timeStr)
summary = timeStr
}
picker.show((context as AppCompatActivity).supportFragmentManager, null)
}
private fun getTimeFormat(): Int {
return if (is24HourFormat(context)) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H
}
private fun getPrefStringPart(index: Int): String? {
val prefStr = PreferenceHelper.getString(key, "").split(SEPARATOR).getOrNull(index)
return if (prefStr != "") prefStr else null
}
private fun getHour(): Int {
return getPrefStringPart(0)?.toInt() ?: 0
}
private fun getMinutes(): Int {
return getPrefStringPart(1)?.toInt() ?: 0
}
private fun getTimeStr(picker: MaterialTimePicker): String {
val hour = TextUtils.toTwoDecimalsString(picker.hour)
val minute = TextUtils.toTwoDecimalsString(picker.minute)
return "$hour$SEPARATOR$minute"
}
private val timeFormat: Int
get() = if (is24HourFormat(context)) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H
companion object {
const val SEPARATOR = ":"
const val DEFAULT_VALUE = "12:00"
}
}

View File

@ -88,4 +88,20 @@ object ImageHelper {
}
return null
}
/**
* Get a squared bitmap with the same width and height from a bitmap
* @param bitmap The bitmap to resize
*/
fun getSquareBitmap(bitmap: Bitmap?): Bitmap? {
bitmap ?: return null
val newSize = minOf(bitmap.width, bitmap.height)
return Bitmap.createBitmap(
bitmap,
(bitmap.width - newSize) / 2,
(bitmap.height - newSize) / 2,
newSize,
newSize
)
}
}

View File

@ -2,9 +2,10 @@ package com.github.libretube.util
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.telephony.TelephonyManager
import androidx.core.content.getSystemService
import androidx.core.os.ConfigurationCompat
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.obj.Country
import java.util.*
@ -15,7 +16,7 @@ object LocaleHelper {
val languageName = PreferenceHelper.getString(PreferenceKeys.LANGUAGE, "sys")
val locale = when {
languageName == "sys" -> Locale.getDefault()
languageName.contains("-") == true -> {
languageName.contains("-") -> {
val languageParts = languageName.split("-")
Locale(
languageParts[0],
@ -38,93 +39,43 @@ object LocaleHelper {
@Suppress("DEPRECATION")
private fun updateResourcesLegacy(context: Context, locale: Locale) {
Locale.setDefault(locale)
val resources: Resources = context.resources
val configuration: Configuration = resources.getConfiguration()
val resources = context.resources
val configuration = resources.configuration
configuration.locale = locale
resources.updateConfiguration(configuration, resources.getDisplayMetrics())
resources.updateConfiguration(configuration, resources.displayMetrics)
}
fun getDetectedCountry(context: Context, defaultCountryIsoCode: String): String {
detectSIMCountry(context)?.let {
if (it != "") return it
}
detectNetworkCountry(context)?.let {
if (it != "") return it
}
detectLocaleCountry(context)?.let {
if (it != "") return it
}
return defaultCountryIsoCode
private fun getDetectedCountry(context: Context): String {
return detectSIMCountry(context)
?: detectNetworkCountry(context)
?: detectLocaleCountry(context)
?: "UK"
}
private fun detectSIMCountry(context: Context): String? {
try {
val telephonyManager =
context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return telephonyManager.simCountryIso
} catch (e: Exception) {
e.printStackTrace()
}
return null
return context.getSystemService<TelephonyManager>()?.simCountryIso?.ifEmpty { null }
}
private fun detectNetworkCountry(context: Context): String? {
try {
val telephonyManager =
context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return telephonyManager.networkCountryIso
} catch (e: Exception) {
e.printStackTrace()
}
return null
return context.getSystemService<TelephonyManager>()?.networkCountryIso?.ifEmpty { null }
}
private fun detectLocaleCountry(context: Context): String? {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return context.resources.configuration.locales[0].country
}
} catch (e: Exception) {
e.printStackTrace()
}
return null
return ConfigurationCompat.getLocales(context.resources.configuration)[0]!!.country
.ifEmpty { null }
}
fun getAvailableCountries(): List<Country> {
val isoCountries = Locale.getISOCountries()
val countries = mutableListOf<Country>()
isoCountries.forEach { countryCode ->
val locale = Locale("", countryCode)
val countryName = locale.displayCountry
countries.add(
Country(
countryName,
countryCode
)
)
}
countries.sortBy { it.name }
return countries
return Locale.getISOCountries()
.map { Country(Locale("", it).displayCountry, it) }
.sortedBy { it.name }
}
fun getAvailableLocales(): List<Country> {
val availableLocales: Array<Locale> = Locale.getAvailableLocales()
val locales = mutableListOf<Country>()
availableLocales.forEach { locale ->
if (locales.filter { it.code == locale.language }.isEmpty()) {
locales.add(
Country(
locale.displayLanguage,
locale.language
)
)
}
}
return locales
return Locale.getAvailableLocales()
.distinctBy { it.language }
.map { Country(it.displayLanguage, it.language) }
.sortedBy { it.name }
}
fun getTrendingRegion(context: Context): String {
@ -132,8 +83,7 @@ object LocaleHelper {
// get the system default country if auto region selected
return if (regionPref == "sys") {
getDetectedCountry(context, "UK")
.uppercase()
getDetectedCountry(context).uppercase()
} else {
regionPref
}

View File

@ -5,7 +5,6 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
@ -14,9 +13,13 @@ import android.os.Bundle
import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import androidx.annotation.DrawableRes
import androidx.core.app.NotificationCompat
import coil.request.ImageRequest
import com.github.libretube.R
import com.github.libretube.api.obj.Streams
import com.github.libretube.compat.PendingIntentCompat
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PLAYER_NOTIFICATION_ID
@ -26,6 +29,7 @@ import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.ui.PlayerNotificationManager.CustomActionReceiver
class NowPlayingNotification(
private val context: Context,
@ -51,11 +55,10 @@ class NowPlayingNotification(
private var playerNotification: PlayerNotificationManager? = null
/**
* The [DescriptionAdapter] is used to show title, uploaderName and thumbnail of the video in the notification
* The [descriptionAdapter] is used to show title, uploaderName and thumbnail of the video in the notification
* Basic example [here](https://github.com/AnthonyMarkD/AudioPlayerSampleTest)
*/
inner class DescriptionAdapter :
PlayerNotificationManager.MediaDescriptionAdapter {
private val descriptionAdapter = object : PlayerNotificationManager.MediaDescriptionAdapter {
/**
* sets the title of the notification
*/
@ -71,23 +74,19 @@ class NowPlayingNotification(
// starts a new MainActivity Intent when the player notification is clicked
// it doesn't start a completely new MainActivity because the MainActivity's launchMode
// is set to "singleTop" in the AndroidManifest (important!!!)
// that's the only way to launch back into the previous activity (e.g. the player view
// 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) {
putExtra(IntentData.openAudioPlayer, true)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
return PendingIntent.getActivity(
context,
0,
intent,
PendingIntentCompat.updateCurrentFlags
)
}
/**
@ -111,27 +110,71 @@ class NowPlayingNotification(
val request = ImageRequest.Builder(context)
.data(streams?.thumbnailUrl)
.target { result ->
bitmap = (result as BitmapDrawable).bitmap
val bm = (result as BitmapDrawable).bitmap
// returns the bitmap on Android 13+, for everything below scaled down to a square
bitmap = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
ImageHelper.getSquareBitmap(bm)
} else {
bm
}
callback.onBitmap(bitmap!!)
}
.build()
// enqueue the thumbnail loading request
ImageHelper.imageLoader.enqueue(request)
// returns the bitmap on Android 13+, for everything below scaled down to a square
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) getSquareBitmap(bitmap) else bitmap
return bitmap
}
override fun getCurrentSubText(player: Player): CharSequence? {
return streams?.uploader
}
}
private fun getSquareBitmap(bitmap: Bitmap?): Bitmap? {
bitmap ?: return null
val newSize = minOf(bitmap.width, bitmap.height)
return Bitmap.createBitmap(
bitmap,
(bitmap.width - newSize) / 2,
(bitmap.height - newSize) / 2,
newSize,
newSize
private val customActionReceiver = object : CustomActionReceiver {
override fun createCustomActions(
context: Context,
instanceId: Int
): MutableMap<String, NotificationCompat.Action> {
return mutableMapOf(
PREV to createNotificationAction(R.drawable.ic_prev_outlined, PREV, instanceId),
NEXT to createNotificationAction(R.drawable.ic_next_outlined, NEXT, instanceId),
REWIND to createNotificationAction(R.drawable.ic_rewind_md, REWIND, instanceId),
FORWARD to createNotificationAction(R.drawable.ic_forward_md, FORWARD, instanceId)
)
}
override fun getCustomActions(player: Player): MutableList<String> {
return mutableListOf(PREV, NEXT, REWIND, FORWARD)
}
override fun onCustomAction(player: Player, action: String, intent: Intent) {
handlePlayerAction(action)
}
}
private fun createNotificationAction(drawableRes: Int, actionName: String, instanceId: Int): NotificationCompat.Action {
val intent: Intent = Intent(actionName).setPackage(context.packageName)
val pendingIntent = PendingIntent.getBroadcast(
context,
instanceId,
intent,
PendingIntentCompat.cancelCurrentFlags
)
return NotificationCompat.Action.Builder(drawableRes, actionName, pendingIntent).build()
}
private fun createMediaSessionAction(@DrawableRes drawableRes: Int, actionName: String): MediaSessionConnector.CustomActionProvider {
return object : MediaSessionConnector.CustomActionProvider {
override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? {
return PlaybackStateCompat.CustomAction.Builder(actionName, actionName, drawableRes).build()
}
override fun onCustomAction(player: Player, action: String, extras: Bundle?) {
handlePlayerAction(action)
}
}
}
/**
@ -139,32 +182,70 @@ class NowPlayingNotification(
*/
private fun createMediaSession() {
if (this::mediaSession.isInitialized) return
mediaSession = MediaSessionCompat(context, this.javaClass.name)
mediaSession.isActive = true
mediaSession = MediaSessionCompat(context, this.javaClass.name).apply {
isActive = true
}
mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector.setQueueNavigator(object : TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(
player: Player,
windowIndex: Int
): MediaDescriptionCompat {
return MediaDescriptionCompat.Builder().apply {
setTitle(streams?.title!!)
setSubtitle(streams?.uploader)
val extras = Bundle()
val appIcon = BitmapFactory.decodeResource(
Resources.getSystem(),
R.drawable.ic_launcher_monochrome
mediaSessionConnector = MediaSessionConnector(mediaSession).apply {
setPlayer(player)
setQueueNavigator(object : TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(
player: Player,
windowIndex: Int
): MediaDescriptionCompat {
return MediaDescriptionCompat.Builder().apply {
setTitle(streams?.title!!)
setSubtitle(streams?.uploader)
val appIcon = BitmapFactory.decodeResource(
context.resources,
R.drawable.ic_launcher_monochrome
)
val extras = Bundle().apply {
putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, appIcon)
putString(MediaMetadataCompat.METADATA_KEY_TITLE, streams?.title!!)
putString(MediaMetadataCompat.METADATA_KEY_ARTIST, streams?.uploader)
}
setIconBitmap(appIcon)
setExtras(extras)
}.build()
}
override fun getSupportedQueueNavigatorActions(player: Player): Long {
return PlaybackStateCompat.ACTION_PLAY_PAUSE
}
})
setCustomActionProviders(
createMediaSessionAction(R.drawable.ic_prev_outlined, PREV),
createMediaSessionAction(R.drawable.ic_next_outlined, NEXT),
createMediaSessionAction(R.drawable.ic_rewind_md, REWIND),
createMediaSessionAction(R.drawable.ic_forward_md, FORWARD)
)
}
}
private fun handlePlayerAction(action: String) {
when (action) {
NEXT -> {
if (PlayingQueue.hasNext()) {
PlayingQueue.onQueueItemSelected(
PlayingQueue.currentIndex() + 1
)
extras.putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, appIcon)
extras.putString(MediaMetadataCompat.METADATA_KEY_TITLE, streams?.title!!)
extras.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, streams?.uploader)
setIconBitmap(appIcon)
setExtras(extras)
}.build()
}
}
})
mediaSessionConnector.setPlayer(player)
PREV -> {
if (PlayingQueue.hasPrev()) {
PlayingQueue.onQueueItemSelected(
PlayingQueue.currentIndex() - 1
)
}
}
REWIND -> {
player.seekTo(player.currentPosition - PlayerHelper.seekIncrement)
}
FORWARD -> {
player.seekTo(player.currentPosition + PlayerHelper.seekIncrement)
}
}
}
/**
@ -190,21 +271,18 @@ class NowPlayingNotification(
playerNotification = PlayerNotificationManager
.Builder(context, PLAYER_NOTIFICATION_ID, BACKGROUND_CHANNEL_ID)
// set the description of the notification
.setMediaDescriptionAdapter(
DescriptionAdapter()
)
.build()
playerNotification?.apply {
setPlayer(player)
setUseNextAction(false)
setUsePreviousAction(false)
setUseStopAction(true)
setColorized(true)
setMediaSessionToken(mediaSession.sessionToken)
setSmallIcon(R.drawable.ic_launcher_lockscreen)
setUseFastForwardActionInCompactView(true)
setUseRewindActionInCompactView(true)
}
.setMediaDescriptionAdapter(descriptionAdapter)
// register the receiver for custom actions, doesn't seem to change anything
.setCustomActionReceiver(customActionReceiver)
.build().apply {
setPlayer(player)
setColorized(true)
setMediaSessionToken(mediaSession.sessionToken)
setSmallIcon(R.drawable.ic_launcher_lockscreen)
setUseNextAction(false)
setUsePreviousAction(false)
setUseStopAction(true)
}
}
/**
@ -224,4 +302,11 @@ class NowPlayingNotification(
) as NotificationManager
notificationManager.cancel(PLAYER_NOTIFICATION_ID)
}
companion object {
private const val PREV = "prev"
private const val NEXT = "next"
private const val REWIND = "rewind"
private const val FORWARD = "forward"
}
}

View File

@ -397,52 +397,47 @@ object PlayerHelper {
*/
@RequiresApi(Build.VERSION_CODES.O)
fun getPiPModeActions(activity: Activity, isPlaying: Boolean, isOfflinePlayer: Boolean = false): ArrayList<RemoteAction> {
val actions: ArrayList<RemoteAction> = ArrayList()
actions.add(
if (!isOfflinePlayer && alternativePiPControls) {
getRemoteAction(
activity,
R.drawable.ic_headphones,
R.string.background_mode,
PlayerEvent.Background
)
} else {
getRemoteAction(
activity,
R.drawable.ic_rewind,
R.string.rewind,
PlayerEvent.Rewind
)
}
val audioModeAction = getRemoteAction(
activity,
R.drawable.ic_headphones,
R.string.background_mode,
PlayerEvent.Background
)
actions.add(
getRemoteAction(
activity,
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
R.string.pause,
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play
)
val rewindAction = getRemoteAction(
activity,
R.drawable.ic_rewind,
R.string.rewind,
PlayerEvent.Rewind
)
actions.add(
if (!isOfflinePlayer && alternativePiPControls) {
getRemoteAction(
activity,
R.drawable.ic_next,
R.string.play_next,
PlayerEvent.Next
)
} else {
getRemoteAction(
activity,
R.drawable.ic_forward,
R.string.forward,
PlayerEvent.Forward
)
}
val playPauseAction = getRemoteAction(
activity,
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
R.string.pause,
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play
)
return actions
val skipNextAction = getRemoteAction(
activity,
R.drawable.ic_next,
R.string.play_next,
PlayerEvent.Next
)
val forwardAction = getRemoteAction(
activity,
R.drawable.ic_forward,
R.string.forward,
PlayerEvent.Forward
)
return if (
!isOfflinePlayer && alternativePiPControls
) {
arrayListOf(audioModeAction, playPauseAction, skipNextAction)
} else {
arrayListOf(rewindAction, playPauseAction, forwardAction)
}
}
/**

View File

@ -6,6 +6,7 @@ import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.extensions.move
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toStreamItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -13,6 +14,7 @@ import kotlinx.coroutines.launch
object PlayingQueue {
private val queue = mutableListOf<StreamItem>()
private var currentStream: StreamItem? = null
private val scope = CoroutineScope(Dispatchers.IO)
/**
* Listener that gets called when the user selects an item from the queue
@ -28,11 +30,11 @@ object PlayingQueue {
fun clear() = queue.clear()
fun add(vararg streamItem: StreamItem) {
streamItem.forEach {
if (currentStream != it) {
if (queue.contains(it)) queue.remove(it)
queue.add(it)
}
for (stream in streamItem) {
if (currentStream?.url?.toID() == stream.url?.toID()) continue
// remove if already present
queue.remove(stream)
queue.add(stream)
}
}
@ -111,7 +113,7 @@ object PlayingQueue {
private fun fetchMoreFromPlaylist(playlistId: String, nextPage: String?) {
var playlistNextPage: String? = nextPage
CoroutineScope(Dispatchers.IO).launch {
scope.launch {
while (playlistNextPage != null) {
RetrofitInstance.authApi.getPlaylistNextPage(
playlistId,
@ -127,7 +129,7 @@ object PlayingQueue {
}
fun insertPlaylist(playlistId: String, newCurrentStream: StreamItem) {
CoroutineScope(Dispatchers.IO).launch {
scope.launch {
try {
val playlist = PlaylistsHelper.getPlaylist(playlistId)
add(*playlist.relatedStreams.orEmpty().toTypedArray())
@ -142,7 +144,7 @@ object PlayingQueue {
private fun fetchMoreFromChannel(channelId: String, nextPage: String?) {
var channelNextPage: String? = nextPage
CoroutineScope(Dispatchers.IO).launch {
scope.launch {
while (channelNextPage != null) {
RetrofitInstance.api.getChannelNextPage(channelId, nextPage!!).apply {
add(*relatedStreams.orEmpty().toTypedArray())
@ -153,15 +155,22 @@ object PlayingQueue {
}
fun insertChannel(channelId: String, newCurrentStream: StreamItem) {
CoroutineScope(Dispatchers.IO).launch {
try {
scope.launch {
runCatching {
val channel = RetrofitInstance.api.getChannel(channelId)
add(*channel.relatedStreams.orEmpty().toTypedArray())
updateCurrent(newCurrentStream)
if (channel.nextpage == null) return@launch
fetchMoreFromChannel(channelId, channel.nextpage)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun insertByVideoId(videoId: String) {
scope.launch {
runCatching {
val streams = RetrofitInstance.api.getStreams(videoId.toID())
add(streams.toStreamItem(videoId))
}
}
}

View File

@ -0,0 +1,45 @@
package com.github.libretube.util
import android.content.Context
import android.content.Intent
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import com.github.libretube.R
import com.github.libretube.constants.IntentData
import com.github.libretube.obj.AppShortcut
import com.github.libretube.ui.activities.MainActivity
object ShortcutHelper {
private val shortcuts = listOf(
AppShortcut("home", R.string.startpage, R.drawable.ic_home),
AppShortcut("trends", R.string.trends, R.drawable.ic_trending),
AppShortcut("subscriptions", R.string.subscriptions, R.drawable.ic_subscriptions),
AppShortcut("library", R.string.library, R.drawable.ic_library)
).reversed()
private fun createShortcut(context: Context, action: String, label: String, icon: IconCompat) {
val shortcut = ShortcutInfoCompat.Builder(context, action)
.setShortLabel(label)
.setLongLabel(label)
.setIcon(icon)
.setIntent(
Intent(context, MainActivity::class.java).apply {
this.action = Intent.ACTION_VIEW
putExtra(IntentData.fragmentToOpen, action)
}
)
.build()
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
}
fun createShortcuts(context: Context) {
ShortcutManagerCompat.getDynamicShortcuts(context).takeIf { it.isEmpty() } ?: return
shortcuts.forEach {
val icon = IconCompat.createWithResource(context, it.drawable)
createShortcut(context, it.action, context.getString(it.label), icon)
}
}
}

View File

@ -22,10 +22,6 @@ object TextUtils {
*/
const val RESERVED_CHARS = "?:\"*|/\\<>\u0000"
fun toTwoDecimalsString(num: Int): String {
return if (num >= 10) num.toString() else "0$num"
}
/**
* Check whether an Url is valid
* @param url The url to test
@ -45,7 +41,7 @@ object TextUtils {
* @param locale The locale to use, otherwise uses system default
* return Localized date string
*/
fun localizeDate(date: String?, locale: Locale? = null): String? {
fun localizeDate(date: String?, locale: Locale): String? {
date ?: return null
// relative time span

View File

@ -0,0 +1,49 @@
package com.github.libretube.util
import android.os.Build
import android.view.WindowManager
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.github.libretube.ui.base.BaseActivity
class WindowHelper(private val activity: BaseActivity) {
fun setFullscreen() = activity.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.navigationBars()
)
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
}
fun unsetFullscreen() = activity.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
}
WindowCompat.setDecorFitsSystemWindows(window, true)
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.show(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.navigationBars()
)
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH
}
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M18,13c0,3.31 -2.69,6 -6,6s-6,-2.69 -6,-6s2.69,-6 6,-6v4l5,-5l-5,-5v4c-4.42,0 -8,3.58 -8,8c0,4.42 3.58,8 8,8s8,-3.58 8,-8H18z" />
</vector>

View File

@ -6,6 +6,6 @@
android:viewportHeight="48">
<path
android:fillColor="#FF000000"
android:fillColor="@color/shortcut_color"
android:pathData="M11,42Q9.75,42 8.875,41.125Q8,40.25 8,39V19.5Q8,18.8 8.325,18.15Q8.65,17.5 9.2,17.1L22.2,7.35Q22.6,7.05 23.05,6.9Q23.5,6.75 24,6.75Q24.5,6.75 24.95,6.9Q25.4,7.05 25.8,7.35L38.8,17.1Q39.35,17.5 39.675,18.15Q40,18.8 40,19.5V39Q40,40.25 39.125,41.125Q38.25,42 37,42H28V28H20V42Z" />
</vector>

View File

@ -6,6 +6,6 @@
android:viewportHeight="48">
<path
android:fillColor="#FF000000"
android:fillColor="@color/shortcut_color"
android:pathData="M23.5,27.85 L32.25,22.25Q32.95,21.8 32.95,21Q32.95,20.2 32.25,19.75L23.5,14.15Q22.75,13.65 21.975,14.075Q21.2,14.5 21.2,15.4V26.6Q21.2,27.5 21.975,27.925Q22.75,28.35 23.5,27.85ZM13,38Q11.8,38 10.9,37.1Q10,36.2 10,35V7Q10,5.8 10.9,4.9Q11.8,4 13,4H41Q42.2,4 43.1,4.9Q44,5.8 44,7V35Q44,36.2 43.1,37.1Q42.2,38 41,38ZM7,44Q5.8,44 4.9,43.1Q4,42.2 4,41V11.5Q4,10.85 4.425,10.425Q4.85,10 5.5,10Q6.15,10 6.575,10.425Q7,10.85 7,11.5V41Q7,41 7,41Q7,41 7,41H36.5Q37.15,41 37.575,41.425Q38,41.85 38,42.5Q38,43.15 37.575,43.575Q37.15,44 36.5,44Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6,18l8.5,-6L6,6v12zM8,9.86L11.03,12 8,14.14L8,9.86zM16,6h2v12h-2z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6l-8.5,6zM16,14.14L12.97,12 16,9.86v4.28z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,5V1l-5,5l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6s-6,-2.69 -6,-6h-2c0,4.42 3.58,8 8,8s8,-3.58 8,-8S16.41,5 11.99,5z" />
</vector>

View File

@ -6,6 +6,6 @@
android:viewportHeight="48">
<path
android:fillColor="#FF000000"
android:fillColor="@color/shortcut_color"
android:pathData="M15.5,7Q14.85,7 14.425,6.575Q14,6.15 14,5.5Q14,4.85 14.425,4.425Q14.85,4 15.5,4H32.5Q33.15,4 33.575,4.425Q34,4.85 34,5.5Q34,6.15 33.575,6.575Q33.15,7 32.5,7ZM8.95,13Q8.3,13 7.875,12.575Q7.45,12.15 7.45,11.5Q7.45,10.85 7.875,10.425Q8.3,10 8.95,10H39.05Q39.7,10 40.125,10.425Q40.55,10.85 40.55,11.5Q40.55,12.15 40.125,12.575Q39.7,13 39.05,13ZM7,44Q5.8,44 4.9,43.1Q4,42.2 4,41V19Q4,17.8 4.9,16.9Q5.8,16 7,16H41Q42.2,16 43.1,16.9Q44,17.8 44,19V41Q44,42.2 43.1,43.1Q42.2,44 41,44ZM22.7,35.85 L29.55,31.25Q30.25,30.8 30.25,30Q30.25,29.2 29.55,28.75L22.7,24.15Q21.95,23.65 21.15,24.075Q20.35,24.5 20.35,25.4V34.65Q20.35,35.55 21.15,35.975Q21.95,36.4 22.7,35.85Z" />
</vector>

View File

@ -4,7 +4,8 @@
android:tint="?attr/colorControlNormal"
android:viewportWidth="52"
android:viewportHeight="52">
<path
android:fillColor="#FF000000"
android:fillColor="@color/shortcut_color"
android:pathData="M50.1,30.56a1.16,1.16 0,0 1,-2 0.82L42.73,26 30.32,36.65a3.39,3.39 0,0 1,-4.92 0l-7.49,-8.54L4.57,39.81a1.13,1.13 0,0 1,-1.64 0l-0.59,-0.59a1.13,1.13 0,0 1,0 -1.64L15.46,19.68a3.39,3.39 0,0 1,4.92 0l7.49,7.49 7.61,-8.78 -4.92,-4.45a1.26,1.26 0,0 1,0.82 -2.11H47.76A2.35,2.35 0,0 1,50 14.3Z" />
</vector>

View File

@ -92,6 +92,29 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:paddingHorizontal="20dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:text="@string/skip_silence"
android:textSize="18sp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/skip_silence"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -434,9 +434,16 @@
<string name="forward">İrəli</string>
<string name="pause">Fasilə ver</string>
<string name="alternative_pip_controls">Alternativ PiP nəzarətləri</string>
<string name="alternative_pip_controls_summary">Yalnız səsi göstər və irəli və geri çevirmək əvəzinə PiP-də idarəetmələri ötür</string>
<string name="alternative_pip_controls_summary">Yalnız səsi göstər və irəli və geri çevirmək əvəzində PiP-də idarəetmələri ötür</string>
<string name="rewind">Geri sar</string>
<string name="audio_player">Səs oynadıcı</string>
<string name="audio_only_mode">Yalnız səs rejimi</string>
<string name="audio_only_mode_summary">LibreTube-u musiqi oynadıcıya dəyiş.</string>
<string name="no_subtitle">Altyazı yoxdur</string>
<string name="download_paused">Endirməyə fasilə verildi</string>
<string name="download_completed">Endirmə tamamlandı</string>
<string name="concurrent_downloads">Maksimal paralel endirmələr</string>
<string name="concurrent_downloads_limit_reached">Maksimal paralel endirmə limitinə çatıldı.</string>
<string name="unknown">Naməlum</string>
<string name="resume">Davam et</string>
</resources>

View File

@ -437,4 +437,13 @@
<string name="forward">Vpřed</string>
<string name="alternative_pip_controls">Alternativní ovládací prvky PiP</string>
<string name="audio_player">Přehrávač zvuku</string>
<string name="no_subtitle">Bez titulků</string>
<string name="download_paused">Stahování pozastaveno</string>
<string name="download_completed">Stahování dokončeno</string>
<string name="concurrent_downloads">Maximální počet souběžných stahování</string>
<string name="concurrent_downloads_limit_reached">Dosažen limit maximálního počtu souběžných stahování.</string>
<string name="unknown">Neznámý</string>
<string name="resume">Pokračovat</string>
<string name="audio_only_mode">Režim pouze zvuku</string>
<string name="audio_only_mode_summary">Proměňte LibreTube v hudební přehrávač.</string>
</resources>

View File

@ -435,4 +435,10 @@
<string name="forward">Vorspulen</string>
<string name="pause">Pause</string>
<string name="alternative_pip_controls">Alternative BiB-Steuerungen</string>
<string name="audio_only_mode">Nur-Audio-Modus</string>
<string name="no_subtitle">Kein Untertitel</string>
<string name="download_paused">Herunterladen pausiert</string>
<string name="download_completed">Herunterladen abgeschlossen</string>
<string name="unknown">Unbekannt</string>
<string name="resume">Fortsetzen</string>
</resources>

View File

@ -437,4 +437,13 @@
<string name="rewind">Rebobinar</string>
<string name="alternative_pip_controls">Controles alternativos de imagen en imagen</string>
<string name="audio_player">Reproductor de audio</string>
<string name="audio_only_mode_summary">Convierte LibreTube en un reproductor de música.</string>
<string name="no_subtitle">Sin subtítulos</string>
<string name="download_paused">Descarga pausada</string>
<string name="download_completed">Descarga completa</string>
<string name="concurrent_downloads">Número máximo de descargas simultáneas</string>
<string name="concurrent_downloads_limit_reached">Límite máximo de descargas simultáneas alcanzado.</string>
<string name="resume">Reanudar</string>
<string name="audio_only_mode">Sólo audio</string>
<string name="unknown">Desconocido</string>
</resources>

View File

@ -439,4 +439,11 @@
<string name="audio_player">ऑडियो प्लेयर</string>
<string name="audio_only_mode">ऑडियो केवल मोड</string>
<string name="audio_only_mode_summary">LibreTube को म्यूजिक प्लेयर में बदलें।</string>
<string name="no_subtitle">कोई उपशीर्षक नहीं</string>
<string name="download_paused">डाउनलोड रोका गया</string>
<string name="download_completed">डाउनलोड पूरा हुआ</string>
<string name="concurrent_downloads">अधिकतम समवर्ती डाउनलोड</string>
<string name="unknown">अनजान</string>
<string name="concurrent_downloads_limit_reached">अधिकतम समवर्ती डाउनलोड सीमा पूरी हो गई है।</string>
<string name="resume">फिर शुरू करें</string>
</resources>

View File

@ -436,4 +436,14 @@
<string name="rewind">Visszatekerés</string>
<string name="alternative_pip_controls">Alternatív PiP irányítás</string>
<string name="alternative_pip_controls_summary">Csak hang és átlépés gombok megjelenítése a PiP-ben az előretekerés és a visszatekerés helyett</string>
<string name="audio_player">Hanglejátszó</string>
<string name="no_subtitle">Nincs felirat</string>
<string name="download_paused">Letöltés szüneteltetve</string>
<string name="download_completed">Letöltés befejezve</string>
<string name="concurrent_downloads">Maximális egyidejű letöltések</string>
<string name="unknown">Ismeretlen</string>
<string name="resume">Folytatás</string>
<string name="concurrent_downloads_limit_reached">Az egyidejű letöltések maximális száma elérve.</string>
<string name="audio_only_mode">Csak hang mód</string>
<string name="audio_only_mode_summary">Változtassa a LibreTube-ot zenelejátszóvá.</string>
</resources>

View File

@ -436,4 +436,14 @@
<string name="pause">Pausa</string>
<string name="alternative_pip_controls">Controlli PiP alternativi</string>
<string name="alternative_pip_controls_summary">Mostra solo l\'audio e salta i controlli nel PiP, invece di avanti e riavvolgi</string>
<string name="audio_player">Riproduttore audio</string>
<string name="no_subtitle">Nessun sottotitolo</string>
<string name="download_paused">Scaricamento in pausa</string>
<string name="download_completed">Scaricamento completato</string>
<string name="concurrent_downloads">Max download simultanei</string>
<string name="concurrent_downloads_limit_reached">Limite massimo di download simultanei raggiunto.</string>
<string name="unknown">Sconosciuto</string>
<string name="resume">Riprendi</string>
<string name="audio_only_mode">Modalità solo audio</string>
<string name="audio_only_mode_summary">Trasforma LibreTube in un riproduttore musicale.</string>
</resources>

View File

@ -436,4 +436,15 @@
<string name="pause">השהיה</string>
<string name="alternative_pip_controls">בקרים חלופיים לתמונה בתוך תמונה</string>
<string name="alternative_pip_controls_summary">להציג בקרי שמע בלבד ודילוג בתמונה בתוך תמונה במקום קדימה ואחורה</string>
<string name="audio_player">נגן שמע</string>
<string name="download_completed">ההורדה הושלמה</string>
<string name="concurrent_downloads">כמות ההורדות המרבית במקביל</string>
<string name="no_subtitle">אין כתובית</string>
<string name="download_paused">ההורדה מושהית</string>
<string name="concurrent_downloads_limit_reached">הגעת למגבלת ההורדות במקביל.</string>
<string name="unknown">לא ידוע</string>
<string name="resume">המשך</string>
<string name="audio_only_mode">מצב שמע בלבד</string>
<string name="audio_only_mode_summary">הפיכת LibreTube לנגן מוזיקה.</string>
<string name="sleep_timer">מתזמן שינה</string>
</resources>

View File

@ -437,4 +437,13 @@
<string name="alternative_pip_controls">ବିକଳ୍ପ PIP ନିୟନ୍ତ୍ରଣ</string>
<string name="alternative_pip_controls_summary">କେବଳ ଅଡିଓ ଦେଖାନ୍ତୁ ଏବଂ ଆଗକୁ ଏବଂ ପଛକୁ ନେବା ପରିବର୍ତ୍ତେ PiP ରେ କଣ୍ଟ୍ରୋଲ୍ ଛାଡିଦିଅ</string>
<string name="audio_player">ଅଡିଓ ପ୍ଲେୟାର</string>
<string name="no_subtitle">କୌଣସି ଉପ-ଆଖ୍ୟା ନାହିଁ</string>
<string name="download_paused">ଡାଉନଲୋଡ୍ ବିରତ ହୋଇଛି</string>
<string name="download_completed">ଡାଉନଲୋଡ୍ ସମାପ୍ତ ହୋଇଛି</string>
<string name="concurrent_downloads">ସର୍ବାଧିକ ଏକକାଳୀନ ଡାଉନଲୋଡ୍</string>
<string name="concurrent_downloads_limit_reached">ସର୍ବାଧିକ ଏକକାଳୀନ ଡାଉନଲୋଡ୍ ସୀମା ପହଞ୍ଚିଲା ।</string>
<string name="unknown">ଅଜ୍ଞାତ</string>
<string name="resume">ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ</string>
<string name="audio_only_mode">କେବଳ ଅଡିଓ ମୋଡ୍</string>
<string name="audio_only_mode_summary">LibreTube କୁ ଏକ ମ୍ୟୁଜିକ୍ ପ୍ଲେୟାରରେ ପରିଣତ କର ।</string>
</resources>

View File

@ -439,4 +439,11 @@
<string name="audio_player">ਆਡੀਓ ਪਲੇਅਰ</string>
<string name="audio_only_mode">ਸਿਰਫ ਆਡੀਓ ਮੋਡ</string>
<string name="audio_only_mode_summary">LibreTube ਨੂੰ ਇੱਕ ਸੰਗੀਤ ਪਲੇਅਰ ਵਿੱਚ ਬਦਲੋ।</string>
<string name="no_subtitle">ਕੋਈ ਉਪਸਿਰਲੇਖ ਨਹੀਂ</string>
<string name="unknown">ਅਗਿਆਤ</string>
<string name="download_paused">ਡਾਊਨਲੋਡ ਰੋਕਿਆ ਗਿਆ</string>
<string name="download_completed">ਡਾਊਨਲੋਡ ਪੂਰਾ ਹੋਇਆ</string>
<string name="resume">ਮੁੜ ਸ਼ੁਰੂ ਕਰੋ</string>
<string name="concurrent_downloads">ਅਧਿਕਤਮ ਸਮਕਾਲੀ ਡਾਊਨਲੋਡ</string>
<string name="concurrent_downloads_limit_reached">ਅਧਿਕਤਮ ਸਮਕਾਲੀ ਡਾਊਨਲੋਡ ਸੀਮਾ ਪੂਰੀ ਹੋ ਗਈ ਹੈ।</string>
</resources>

View File

@ -435,6 +435,15 @@
<string name="forward">Do przodu</string>
<string name="pause">Wstrzymaj</string>
<string name="alternative_pip_controls">Alternatywne sterowanie PiP</string>
<string name="alternative_pip_controls_summary">Zastąp przyciski „przewiń do tyłu” i „przwiń do przodu” na „tylko dźwięk” i „pomiń film”.</string>
<string name="alternative_pip_controls_summary">Wyświetl sterowanie odtwarzaniem dźwięku w tle i pomijania filmu zamiast przycisków przewijania do tyłu i przodu.</string>
<string name="audio_player">Odtwarzacz dźwięku</string>
<string name="download_paused">Pobieranie wstrzymane</string>
<string name="download_completed">Pobieranie ukończone</string>
<string name="concurrent_downloads">Maks. równoczesnych pobierań</string>
<string name="concurrent_downloads_limit_reached">Osiągnięto limit maksymalnego jednoczesnego pobierania.</string>
<string name="unknown">Nieznane</string>
<string name="resume">Wznów</string>
<string name="no_subtitle">Bez napisów</string>
<string name="audio_only_mode_summary">Zmień LibreTube w odtwarzacz muzyczny.</string>
<string name="audio_only_mode">Tryb „tylko dźwięk”</string>
</resources>

View File

@ -437,4 +437,15 @@
<string name="alternative_pip_controls">Controles PiP alternativos</string>
<string name="alternative_pip_controls_summary">Mostrar controles de apenas áudio e pular no PiP em vez de avançar e retroceder</string>
<string name="audio_player">Player de Áudio</string>
<string name="no_subtitle">Sem legenda</string>
<string name="download_paused">Download pausado</string>
<string name="download_completed">Download concluído</string>
<string name="concurrent_downloads">Máximo de downloads simultâneos</string>
<string name="concurrent_downloads_limit_reached">Limite máximo de downloads simultâneos atingido.</string>
<string name="unknown">Desconhecido</string>
<string name="resume">Retomar</string>
<string name="audio_only_mode_summary">Transforme o LibreTube em um reprodutor de música.</string>
<string name="audio_only_mode">Modo somente áudio</string>
<string name="sleep_timer">Temporizador</string>
<string name="skip_silence">Pular silêncio</string>
</resources>

View File

@ -437,4 +437,13 @@
<string name="alternative_pip_controls">Controlos PiP alternativos</string>
<string name="alternative_pip_controls_summary">Mostrar apenas áudio e saltar controlos em PiP em vez de avançar e rebobinar</string>
<string name="audio_player">Reprodutor de áudio</string>
<string name="no_subtitle">Sem legendas</string>
<string name="download_paused">Descarga em pausa</string>
<string name="audio_only_mode">Apenas áudio</string>
<string name="audio_only_mode_summary">Transforme LibreTube num reprodutor de músicas.</string>
<string name="concurrent_downloads">Número de transferências simultâneas</string>
<string name="unknown">Desconhecido</string>
<string name="resume">Continuar</string>
<string name="download_completed">Descarga terminada</string>
<string name="concurrent_downloads_limit_reached">Atingiu o limite máximo de descargas em simultâneo.</string>
</resources>

View File

@ -436,4 +436,14 @@
<string name="pause">Пауза</string>
<string name="add_to_bookmarks">Добавить в закладки</string>
<string name="remove_bookmark">Удалить закладку</string>
<string name="audio_player">Аудиоплеер</string>
<string name="no_subtitle">Без субтитров</string>
<string name="download_paused">Загрузка приостановлена</string>
<string name="download_completed">Загрузка завершена</string>
<string name="concurrent_downloads">Максимальное количество одновременных загрузок</string>
<string name="concurrent_downloads_limit_reached">Достигнут максимальный предел одновременных загрузок.</string>
<string name="unknown">Неизвестно</string>
<string name="resume">Продолжить</string>
<string name="audio_only_mode">Режим только аудио</string>
<string name="audio_only_mode_summary">Превратите LibreTube в музыкальный проигрыватель.</string>
</resources>

View File

@ -0,0 +1,449 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="library">ලේඛනාලය</string>
<string name="yes">ඔව්</string>
<string name="choose_quality_dialog">ගුණත්වය</string>
<string name="search_hint">සොයන්න</string>
<string name="videos">වීඩියෝ</string>
<string name="subscribe">දායකවන්න</string>
<string name="share">බෙදාගන්න</string>
<string name="download">බාගත කරන්න</string>
<string name="save">සුරකින්න</string>
<string name="username">පරිශීලක නාමය</string>
<string name="login">ඇතුළු වන්න</string>
<string name="register">ලියාපදිංචි වන්න</string>
<string name="logout">වරනය වන්න</string>
<string name="cancel">අවලංගු කරන්න</string>
<string name="loggedout">වරනය වුනා.</string>
<string name="already_logged_in">දැනටමත් පුරනය වී ඇත. ඔබට ඔබගේ ගිණුමෙන් වරනය විය හැක.</string>
<string name="login_first">කරුණාකර පුරනය වී නැවත උත්සාහ කරන්න.</string>
<string name="customInstance">අභිරුචිය පරිදි</string>
<string name="region">කලාපය</string>
<string name="please_login">කරුණාකර පළමුව පුරනය වන්න හෝ සැකසීම් තුළ ලියාපදිංචි වන්න.</string>
<string name="importsuccess">දායක විය</string>
<string name="cannotDownload">මෙම ප්‍රවාහය බාගත කළ නොහැක.</string>
<string name="dlcomplete">බාගත කිරීම සම්පූර්ණයි.</string>
<string name="downloadfailed">බාගත කිරීම අසමත් විය.</string>
<string name="vlc">VLC හි විවෘත කරන්න</string>
<string name="import_from_yt">දායකත්වයන් ආනයනය කරන්න</string>
<string name="import_from_yt_summary">YouTube හෝ NewPipe වෙතින්</string>
<string name="app_theme">පෙනුම</string>
<string name="unknown_error">ජාල දෝෂයකි.</string>
<string name="error">මොකක්හරි වැරැද්දක් වෙලා.</string>
<string name="grid">ජාලකයේ තීරු ගණන</string>
<string name="emptyList">මෙහි කිසිවක් නැහැ.</string>
<string name="success">කළා.</string>
<string name="fail">අසාර්ථකයි :(</string>
<string name="deletePlaylist">වාදන ලැයිස්තුව මකන්න</string>
<string name="createPlaylist">වාදන ලැයිස්තුව සාදන්න</string>
<string name="about">ගැන</string>
<string name="changeLanguage">භාෂාව</string>
<string name="systemLanguage">පද්ධතිය</string>
<string name="systemDefault">පද්ධතිය</string>
<string name="lightTheme">එළිය</string>
<string name="darkTheme">අඳුරු</string>
<string name="subscribers">%1$s දායක වන්නන්</string>
<string name="settings">සැකසුම්</string>
<string name="location">ස්ථානය</string>
<string name="instance">සේවාදායකය</string>
<string name="customization">ගැලපීම්</string>
<string name="website">වෙබ් අඩවිය</string>
<string name="videoCount">%1$s වීඩියෝ</string>
<string name="noInternet">මුලින්ම අන්තර්ජාලයට සම්බන්ධ වෙන්න.</string>
<string name="retry">නැවත උත්සාහ කරන්න</string>
<string name="comments">අදහස්</string>
<string name="choose_filter">සෙවුම් පෙරහන තෝරන්න</string>
<string name="channels">නාලිකා</string>
<string name="all">සියලුම</string>
<string name="okay">හරි</string>
<string name="search_history">සෙවුම් ඉතිහාසය</string>
<string name="clear_history">ඉතිහාසය හිස් කරන්න</string>
<string name="music_songs">YT Music ගීත</string>
<string name="music_videos">YT Music වීඩියෝ</string>
<string name="music_albums">YT Music ඇල්බම</string>
<string name="defaultTab">පෙරනිමි පිම්ම</string>
<string name="segment_skipped">කොටස මඟ හැරිය</string>
<string name="sponsorblock_state">සක්‍රිය</string>
<string name="category_sponsor">අනුග්‍රාහකයා</string>
<string name="category_selfpromo">නොගෙවූ/ස්වයං ප්‍රවර්ධන</string>
<string name="category_interaction">අන්තර්ක්‍රියා මතක් කිරීම (කැමති සහ දායක වන්න)</string>
<string name="category_intro">විරාම/හඳුන්වාදීමේ සජීවිකරණය</string>
<string name="category_outro">අවසන් කාඩ්පත් සහ දායක ලැයිස්තුව</string>
<string name="category_outro_description">අවසානයට පසුව තොරතුරු. තොරතුරු සහිත නිගමන සඳහා නොවේ.</string>
<string name="category_filler">අදාළ නොවන කොටස්/විහිළු</string>
<string name="sponsorblock">SponsorBlock</string>
<string name="playlists">වාදන ලැයිස්තු</string>
<string name="music_playlists">YT Music වාදන ලැයිස්තු</string>
<string name="category_selfpromo_description">නොගෙවූ හෝ ස්වයං ප්‍රවර්ධනය හැර \"අනුග්‍රාහකයා\" හා සමානයි. මෙයට වෙළඳ භාණ්ඩ, පරිත්‍යාග, හෝ ඔවුන් සහයෝගීව කටයුතු කළ අය පිළිබඳ තොරතුරු පිළිබඳ කොටස් ඇතුළත් වේ.</string>
<string name="category_preview">පෙරදසුන/සාරාංශය</string>
<string name="license">බලපත්රය</string>
<string name="material_you">Material You</string>
<string name="sponsorblock_notifications">දැනුම්දීම්</string>
<string name="app_icon">නිරූපකය</string>
<string name="enabled">සක්‍රිය</string>
<string name="disabled">අක්‍රිය</string>
<string name="piped">Piped</string>
<string name="youtube">YouTube</string>
<string name="update_available">නිකුතු අංකය %1$s ඇත</string>
<string name="update_available_text">එය බාගත කිරීමට GitHub හි නිකුතු වෙත යන්නද\?</string>
<string name="appearance">පෙනුම</string>
<string name="downloads">බාගත කිරීම්</string>
<string name="video_format">වීඩියෝ ආකෘතිය</string>
<string name="download_directory">බාගත කරන ස්ථානය</string>
<string name="download_directory_summary">බාගත කළ මාධ්‍ය ගබඩා කරන තැන.</string>
<string name="contributing">දායක වන්න</string>
<string name="update">නව අනුවාදයක් සොයන්න</string>
<string name="update_summary">යාවත්කාලීන කිරීමට ඹබන්න</string>
<string name="app_uptodate">නවතම අනුවාදය ධාවනය කරමින් සිටියි.</string>
<string name="advanced">උසස්</string>
<string name="player">වාදකය</string>
<string name="appearance_summary">ඔබගේ අභිමතය පරිදි යෙදුම සකසන්න.</string>
<string name="advanced_summary">බාගැනීම්, සහ යළි පිහිටුවන්න</string>
<string name="live">සජීවි</string>
<string name="authors">කතුවරුන්</string>
<string name="download_folder_summary">බාගත කළ මාධ්‍ය ගබඩා කර ඇති ෆෝල්ඩරයේ නම.</string>
<string name="internal_storage">අභ්යන්තර ගබඩාව</string>
<string name="downloads_directory">ෆෝල්ඩරය බාගත කරන්න</string>
<string name="sdcard">SD කාඩ්</string>
<string name="shareTo">URL එක බෙදාගන්න</string>
<string name="views_placeholder">බැලීම්</string>
<string name="views">%1$s බැලීම්</string>
<string name="defaultIcon">පෙරනිමිය</string>
<string name="color_accent">අවධාරණ</string>
<string name="color_yellow">කහ පාට</string>
<string name="color_green">කොළ පාට</string>
<string name="color_purple">දම් පාට</string>
<string name="oledTheme">කළු පාට</string>
<string name="color_blue">නිල් පාට</string>
<string name="color_red">රතු පාට</string>
<string name="playback_speed">වාදන වේගය</string>
<string name="fireIcon">විලාසිතාමය ගින්න</string>
<string name="torchIcon">නවීන පන්දම</string>
<string name="flameIcon">පියාඹන දැල්ල</string>
<string name="instance_summary">Piped, පුරනය සහ දායකත්ව</string>
<string name="instance_name">සේවාදායකයේ නම</string>
<string name="instance_api_url">සේවාදායක API වෙත URL</string>
<string name="addInstance">සේවාදායකයක් එකතු කරන්න</string>
<string name="clear_customInstances">එකතු කිරීම් මකන්න</string>
<string name="version">අනුවාදය %1$s</string>
<string name="about_summary">LibreTube කණ්ඩායම සහ ඒ සියල්ල සිදුවන්නේ කෙසේදැයි දැන ගන්න.</string>
<string name="related_streams">අදාළ අන්තර්ගත</string>
<string name="show_chapters">පරිච්ඡේද පෙන්වන්න</string>
<string name="hide_chapters">පරිච්ඡේද සඟවන්න</string>
<string name="gradientIcon">Glib ශ්‍රේණියේ වර්ණය</string>
<string name="birdIcon">පියාඹන කුරුල්ලා</string>
<string name="buffering_goal">පූර්ව ප්‍රෙව්ශය වෙමින්</string>
<string name="playerVideoFormat">වාදකය සඳහා වීඩියෝ ආකෘතිය</string>
<string name="no_audio">ශ්‍රව්‍ය නැත</string>
<string name="no_subtitle">උපසිරැසි නැත</string>
<string name="audio">ශ්‍රව්‍ය</string>
<string name="downloading">බාගත වෙමින්…</string>
<string name="download_paused">බාගත කිරීම නවතා ඇත</string>
<string name="concurrent_downloads">උපරිම සමකාලීන බාගත කිරීම්</string>
<string name="concurrent_downloads_limit_reached">උපරිම සමකාලීන බාගත කිරීම් සීමාව ළඟා විය.</string>
<string name="pause">නවත්වන්න</string>
<string name="resume">අරඹන්න</string>
<string name="hideTrendingPage">නැගී එන පිටුව සඟවන්න</string>
<string name="instance_frontend_url">සේවාදායක ඉදිරිපස URL</string>
<string name="quality">ගුණත්වය</string>
<string name="player_summary">පෙරනිමි සහ හැසිරීම</string>
<string name="seek_increment">වර්ධක අන්වේෂණය කරන්න</string>
<string name="player_autoplay">ස්වයංක්‍රීය වාදනය</string>
<string name="reset">පෙරනිමි ප්‍රත්‍යර්පණය කරන්න</string>
<string name="deleteAccount">ගිණුම මකන්න</string>
<string name="deleteAccount_summary">ඔබගේ Piped ගිණුම මකන්න</string>
<string name="account">ගිණුම</string>
<string name="restore">ප්‍රත්‍යර්පණය කරන්න</string>
<string name="watch_history">නැරඹුම් ඉතිහාසය</string>
<string name="auth_instance">සත්‍යතාව තහවුරු කිරීමේ සේවාදායකය</string>
<string name="auth_instances">සත්‍යතාව තහවුරු කිරීමට සේවාදායකයක් තෝරන්න</string>
<string name="hls">HLS</string>
<string name="github">GitHub</string>
<string name="audio_video">ශ්රව්ය සහ වීඩියෝ</string>
<string name="fullscreen_orientation">සම්පූර්ණ තිර නැඹුරුව</string>
<string name="landscape">භූ දර්ශනය</string>
<string name="community">ප්‍රජාව</string>
<string name="discord">Discord</string>
<string name="matrix">Matrix</string>
<string name="telegram">Telegram</string>
<string name="reddit">Reddit</string>
<string name="twitter">Twitter</string>
<string name="turnInternetOn">කරුණාකර අන්තර්ජාලයට සම්බන්ධ වීමට Wi-Fi හෝ ජංගම දත්ත ක්‍රියාත්මක කරන්න.</string>
<string name="open">විවෘත කරන්න…</string>
<string name="chapters">පරිච්ඡේද</string>
<string name="require_restart">යෙදුම නැවත පටන් ගැනීම අවශ්‍යයි</string>
<string name="navLabelVisibility">ලේබල් දෘශ්‍යතාව</string>
<string name="selected">තෝරා ගන්නා ලදී</string>
<string name="never">කවදාවත් නැහැ</string>
<string name="autoRotatePlayer">ස්වයං-පූර්ණ තිරය</string>
<string name="pure_theme">තනි පෙනුම</string>
<string name="pure_theme_summary">තනි සුදු/කළු පෙනුම</string>
<string name="data_saver_mode">දත්ත සුරැකීමේ ප්‍රකාරය</string>
<string name="data_saver_mode_summary">සිඟිති රූ සහ අනෙකුත් පින්තූර මඟ හරින්න.</string>
<string name="watch_history_summary">නැරඹූ වීඩියෝ දේශීයව මතක තබා ගන්න</string>
<string name="history_summary">නැරඹුම් සහ සෙවුම් ඉතිහාසය</string>
<string name="reset_watch_positions">ප්‍රත්‍යාරම්භ කරන්න</string>
<string name="change_playback_speed">වාදන වේගය</string>
<string name="autoRotatePlayer_summary">උපාංගය හැරවූ විට සම්පූර්ණ තිර වාදනය.</string>
<string name="watch_positions_title">මතක වාදන ස්ථාන</string>
<string name="update_now">නව LibreTube අනුවාදය දැන් පිහිටු වන්නද\?</string>
<string name="general_summary">භාෂාව සහ ප්‍රදේශය</string>
<string name="caption_settings">සිරස්තල</string>
<string name="playerAudioFormat">වාදකය සඳහා ශ්‍රව්‍ය ආකෘතිය</string>
<string name="playerAudioQuality">ශ්‍රව්‍ය ගුණත්වය</string>
<string name="best_quality">විශිෂ්ට</string>
<string name="worst_quality">නරකම</string>
<string name="notify_new_streams">නව ප්‍රවාහ සඳහා දැනුම්දීම්</string>
<string name="checking_frequency">බලන වාර ගණන…</string>
<string name="new_streams_count">%1$s නව ප්‍රවාහ තිබේ</string>
<string name="new_streams_by">%1$s විසින් නව ප්‍රවාහ…</string>
<string name="irreversible">ඔබට විශ්වාසද\? මෙය පසුගමනය කළ නොහැක!</string>
<string name="most_recent">අලුත්ම</string>
<string name="least_recent">පැරණිම</string>
<string name="most_views">බොහෝ බැලීම්</string>
<string name="least_views">අඩුම බැලීම්</string>
<string name="channel_name_az">නාලිකාවේ නම (A-Z)</string>
<string name="channel_name_za">නාලිකාවේ නම (Z-A)</string>
<string name="sort">තේරීම</string>
<string name="network_all">සියලුම</string>
<string name="network_metered">සීමිත</string>
<string name="network_wifi">Wi-Fi මත පමණි</string>
<string name="translate">පරිවර්තනය</string>
<string name="no_search_result">ප්‍රතිපල නැත.</string>
<string name="error_occurred">දෝෂයකි</string>
<string name="copied">පිටපත් කර ඇත</string>
<string name="share_with_time">කාල කේතය සමඟ බෙදා ගන්න</string>
<string name="export_subscriptions">දායකත්වයන් අපනයනය කරන්න</string>
<string name="skip_buttons_summary">ඊළඟ හෝ පෙර වීඩියෝවට යාමට බොත්තම් පෙන්වන්න.</string>
<string name="history_size">උපරිම ඉතිහාස ප්‍රමාණය</string>
<string name="background_mode">පසුබිම් ප්‍රකාරය</string>
<string name="misc">වෙනත්</string>
<string name="break_reminder">විවේක මතක් කිරීම</string>
<string name="take_a_break">විවේකයක් ගැනීමට කාලයයි</string>
<string name="yt_shorts">කෙටි වීඩියෝ</string>
<string name="no_subtitles_available">උපසිරැසි නොමැත</string>
<string name="repeat_mode">නැවත ධාවනය කිරීම් ප්‍රකාරය</string>
<string name="resize_mode_fit">සුදුසු</string>
<string name="resize_mode_fill">පුරවන්න</string>
<string name="playingOnBackground">පසුබිමේ වාදනය වෙමින්…</string>
<string name="repeat_mode_current">දැන්</string>
<string name="backup_restore">උපස්ථ සහ ප්‍රත‍්‍යර්පණ කිරීම</string>
<string name="backup">උපස්ථ කිරීම</string>
<string name="picture_in_picture">පින්තූරයේ පින්තූරය</string>
<string name="maximum_image_cache">උපරිම පින්තූර නිහිත ප්‍රමාණය</string>
<string name="open_copied">විවෘත කරන්න</string>
<string name="break_reminder_time">මතක් කිරීමට පෙර මිනිත්තු ගණන</string>
<string name="device_info">උපාංග තොරතුරු</string>
<string name="audio_video_summary">ගුණත්වය සහ ආකෘතිය</string>
<string name="delete">බාගත කිරීම් වලින් මකන්න</string>
<string name="trending_layout">විකල්ප නැගී එන පිරිසැලසුම</string>
<string name="wifi">Wi-Fi</string>
<string name="mobile_data">ජංගම දත්ත</string>
<string name="new_videos_badge_summary">අලුත් වීඩියෝ තිබේ නම් ප්‍රමාණය සමඟ ලාංඡනයක් පෙන්වන්න.</string>
<string name="skip_segment">කොටස මඟ හරින්න</string>
<string name="sb_skip_manual">අතින් මඟ හරින්න</string>
<string name="local_subscriptions">දේශීයව ගබඩා කර ඇති දායකත්වයන්</string>
<string name="preferences">සැකසුම්</string>
<string name="backup_customInstances">වෙනත් සේවාදායකයක්</string>
<string name="save_feed">පසුබිමේ නවතම වීඩියෝ පූරණය කරන්න</string>
<string name="navigation_bar">මං සෙවුම් තීරුව</string>
<string name="select_at_least_one">කරුණාකර අවම වශයෙන් එක් අයිතමයක් තෝරන්න</string>
<string name="progressive_load_interval">ප්‍රගතිශීලීව ප්‍රවේශනය වීමේ විරාම ප්‍රමාණය</string>
<string name="progressive_load_interval_summary">අඩු අගයක් මුල් වීඩියෝ ප්‍රවේශනය වේගවත් කළ හැක.</string>
<string name="playback_pitch">ස්වරය</string>
<string name="filename">ගොනු නාමය</string>
<string name="invalid_filename">වලංගු නොවන ගොනු නාමයකි!</string>
<string name="recentlyUpdated">මෑතකදී යාවත්කාලීන කරන ලද</string>
<string name="recentlyUpdatedReversed">මෑතකදී යාවත්කාලීන කරන ලද (ප්‍රතිලෝම)</string>
<string name="show_more">තව පෙන්වන්න</string>
<string name="play_next">ඊළඟ වීඩියෝව වාදනය කරන්න</string>
<string name="added_to_playlist">වාදන ලැයිස්තුවට එක් කරන ලදී</string>
<string name="queue">පෝලිම</string>
<string name="livestreams">සජීවී ප්‍රවාහ</string>
<string name="alternative_videos_layout">විකල්ප වීඩියෝ පිරිසැලසුම</string>
<string name="defaultIconLight">පෙරනිමි එළිය</string>
<string name="confirm_unsubscribe">ඔබට %1$s ගෙ දායකත්වයෙන් ඉවත් වීමට අවශ්‍ය බව විශ්වාසද\?</string>
<string name="confirm_unsubscribing">දායකත්වයෙන් ඉවත් වීම තහවුරු කරන්න</string>
<string name="time">කාලය</string>
<string name="notification_time">දැනුම්දීමේ කාලය</string>
<string name="notification_time_summary">දැනුම්දීම් පෙන්වීමට අවසර දී ඇති කාල පරාසය.</string>
<string name="alternative_trending_layout">විකල්ප නැගී එන පිරිසැලසුම</string>
<string name="navbar_order">පිළිවෙල</string>
<string name="layout">පිරිසැලසුම</string>
<string name="audio_track">ශ්‍රව්‍ය පථය</string>
<string name="default_audio_track">පෙරනිමිය</string>
<string name="unsupported_file_format">සහාය නොදක්වන ගොනු ආකෘතියකි!</string>
<string name="hls_instead_of_dash">HLS භාවිතා කරන්න</string>
<string name="auto_quality">ස්වයං</string>
<string name="limit_to_runtime">ධාවන කාලයට සීමා කරන්න</string>
<string name="open_queue_from_notification">දැනුම්දීමෙන් පෝලිම විවෘත කරන්න</string>
<string name="trends">ප්‍රවණතා</string>
<string name="featured">විශේෂාංගගත</string>
<string name="trending">දැන් නැගී එන්නේ කුමක්ද</string>
<string name="bookmarks">පොත් සලකුණු</string>
<string name="clear_bookmarks">පොත් සලකුණු හිස් කරන්න</string>
<string name="bookmarks_empty">තවමත් පොත් සලකුණු නොමැත!</string>
<string name="select_other_start_tab">කරුණාකර පළමුව වෙනත් ආරම්භක පිම්මක් තෝරන්න!</string>
<string name="brightness">දීප්තිය</string>
<string name="volume">ශබ්ද හඩ</string>
<string name="auto">ස්වයං</string>
<string name="swipe_controls">ස්වයිප් පාලන</string>
<string name="swipe_controls_summary">දීප්තිය සහ ශබ්ද හඩ සීරුමාරු කිරීමට ස්වයිප් අභිනය භාවිත කරන්න.</string>
<string name="pinch_control">කෙනිත්තීම අභිනය පාලනය</string>
<string name="pinch_control_summary">විශාලනය කිරීමට/පිටතට කිරීමට කෙනිත්තීම අභිනය භාවිත කරන්න.</string>
<string name="defaults">පෙරනිමි</string>
<string name="pop_up">උත්පතන</string>
<string name="theme_monochrome">අවම ඒකවර්ණ</string>
<string name="captions_size">සිරස්තල ප්‍රමාණය</string>
<string name="play_all">සියල්ල වාදනය කරන්න</string>
<string name="all_caught_up_summary">ඔබ නව වීඩියෝ සියල්ලම දැක ඇත</string>
<string name="app_backup">යෙදුම් උපස්ථ</string>
<string name="exportsuccess">අපනයනය කරන ලදී.</string>
<string name="privacy_alert">ඇවිරීම් ඇඟවීම</string>
<string name="username_email">නිර්දේශ නොකළ විද්‍යුත් තැපැල් ලිපිනයක් සමඟ ඉදිරියට යන්නද\?</string>
<string name="proceed">ඉදිරියට යන්න</string>
<string name="play_latest_videos">නවතම වීඩියෝ වාදනය කරන්න</string>
<string name="nothing_selected">කිසිවක් තෝරා නැත!</string>
<string name="color_violet">ජම්බූල පාට</string>
<string name="import_playlists">වාදන ලැයිස්තු ආනයනය කරන්න</string>
<string name="playlistUrl">වාදන ලැයිස්තු URL</string>
<string name="audio_player">ශ්‍රව්‍ය වාදකය</string>
<string name="audio_only_mode_summary">LibreTube සංගීත වාදකයක් බවට පත් කරන්න.</string>
<string name="hide_watched_from_feed_summary">දායකත්ව පිම්මෙහි 90%කට වඩා නරඹන වීඩියෝ නොපෙන්වන්න.</string>
<string name="pause_on_quit">ඉවත් වීමේදී නවත්වන්න</string>
<string name="shuffle">කලවම් කරන්න</string>
<string name="add_to_bookmarks">පොත් සලකුණු වලට එකතු කරන්න</string>
<string name="rewind">ආපස්සට යන්න</string>
<string name="forward">ඉදිරියට යන්න</string>
<string name="alternative_pip_controls">විකල්ප PiP පාලන</string>
<string name="audio_only_mode">ශ්‍රව්‍ය පමණක් ප්‍රකාරය</string>
<string name="download_channel_description">මාධ්‍ය බාගත කිරීමේදී දැනුම්දීමක් පෙන්වයි.</string>
<string name="background_channel_name">පසුබිම් ප්‍රකාරය</string>
<string name="push_channel_description">නව ප්‍රවාහ පවතින විට දැනුම්දීමක් පෙන්වයි.</string>
<string name="all_caught_up">ඹබ සියල්ල බලා ඇත</string>
<string name="startpage">මුල්</string>
<string name="password">මුරපදය</string>
<string name="subscriptions">දායකත්ව</string>
<string name="unsubscribe">දායක නොවන්න</string>
<string name="loggedIn">පුරනය වී ඇත.</string>
<string name="registered">ලියාපදිංචි. දැන් ඔබට නාලිකා වලට දායක විය හැක.</string>
<string name="instances">තෝරන්න…</string>
<string name="login_register">පුරනය වන්න / ලියාපදිංචි වන්න</string>
<string name="subscribeIsEmpty">මුලින්ම නාලිකා කිහිපයකට දායක වෙන්න.</string>
<string name="dlisinprogress">තවත් බාගත කිරීමක් දැනටමත් සිදු වෙමින් පවතී, කරුණාකර එය අවසන් වන තෙක් රැඳී සිටින්න.</string>
<string name="vlcerror">VLC හි විවෘත කළ නොහැක. එය පිහිටුවා නැති විය හැකිය.</string>
<string name="empty">ඔබ පරිශීලක නාමයක් සහ මුරපදයක් ඇතුළත් කළ යුතුය.</string>
<string name="server_error">සේවාදායකයේ ගැටලුවක් තිබේ. වෙනත් සේවාදායකයක් උත්සාහ කරන්නද\?</string>
<string name="notgmail">මෙය Piped ගිණුමක් සඳහා ය.</string>
<string name="defres">වීඩියෝ විභේදනය</string>
<string name="playlistName">වාදන ලැයිස්තුවේ නම</string>
<string name="history">ඉතිහාසය</string>
<string name="sponsorblock_summary">https://sponsor.ajay.app API භාවිත කරයි</string>
<string name="category_segments">කොටස්</string>
<string name="category_interaction_description">අතරතුර කැමති වීමට, දායක වීමට හෝ අනුගමනය කිරීමට කෙටි මතක් කිරීමක් ඇති විට. දිගු හෝ විශේෂිත දෙයක් ගැන නම්, ඒ වෙනුවට එය ස්වයං ප්‍රවර්ධනයකි.</string>
<string name="category_intro_description">සැබෑ අන්තර්ගතයක් නොමැති විරාමයක්. නැවතීමක්, ස්ථිතික රාමුවක්, පුනරාවර්තන සජීවිකරණයක් විය හැකිය. තොරතුරු අඩංගු සංක්‍රාන්ති සඳහා භාවිතා නොකළ යුතුය.</string>
<string name="category_music_offtopic_description">සංගීත වීඩියෝවල භාවිතය සඳහා පමණි. එය නිල මිශ්‍රණවල කොටසක් නොව වීඩියෝවේ කොටස් ආවරණය කළ යුතුය. අවසානයේදී, වීඩියෝව Spotify හෝ වෙනත් ඕනෑම මිශ්‍ර අනුවාදයක් හැකිතාක් සමීපව සමාන විය යුතුය, නැතහොත් කතා කිරීම හෝ වෙනත් අවධානය වෙනතකට යොමු කිරීම අඩු කළ යුතුය.</string>
<string name="category_filler_description">වීඩියෝවේ ප්‍රධාන අන්තර්ගතය තේරුම් ගැනීමට අවශ්‍ය නොවන ෆිලර් හෝ හාස්‍යය සඳහා පමණක් එක් කළ කොටස් සඳහා.</string>
<string name="category_music_offtopic">ගීත: ගීතය නොවන කොටස</string>
<string name="app_behavior">හැසිරීම</string>
<string name="legacyIcon">අහිමි වූ උරුමය</string>
<string name="shapedIcon">මෝඩ හැඩැති</string>
<string name="customInstance_summary">එකතු කරන්න…</string>
<string name="empty_instance">නම සහ API URL එක පුරවන්න.</string>
<string name="playOnBackground">පසුබිමේ වාදනය කරන්න</string>
<string name="video_format_summary">ශ්‍රව්‍ය සහ දෘශ්‍ය යන දෙකම බාගත කර ඇත්නම් ගොනු පරිවර්තනය කිරීම.</string>
<string name="no_update_available">ඔබ නවතම අනුවාදය ධාවනය කරමින් සිටියි.</string>
<string name="no_replies">මෙම අදහසට පිළිතුරු නොමැත.</string>
<string name="music_directory">සංගීත ෆෝල්ඩරය</string>
<string name="donate">පරිත්‍යාග කරන්න</string>
<string name="download_folder">නම</string>
<string name="movies_directory">චිත්‍රපට ෆෝල්ඩරය</string>
<string name="invalid_url">කරුණාකර වැඩ කරන URL එකක් ඇතුළු කරන්න</string>
<string name="related_streams_summary">ඔබ නරඹන දේ හා සමාන ප්‍රවාහ පෙන්වන්න.</string>
<string name="sb_markers">සලකුණු</string>
<string name="video">වීඩියෝ</string>
<string name="buffering_goal_summary">වීඩියෝව බෆර කිරීමට දෙන උපරිම තත්පර ගණන.</string>
<string name="no_video">වීඩියෝ නැත</string>
<string name="download_completed">බාගත කිරීම සම්පූර්ණයි</string>
<string name="unknown">නොදන්නා</string>
<string name="behavior">හැසිරීම</string>
<string name="pauseOnScreenOff">ස්වයං නැවැත්වීම</string>
<string name="clonePlaylist">වාදන ලැයිස්තුව ක්ලෝනය කරන්න</string>
<string name="autoplay_summary">වත්මන් වීඩියෝවෙන් පසුව ඊළඟ වීඩියෝව ස්වයංක්‍රීයව වාදනය කරන්න.</string>
<string name="reset_message">සියලු සැකසුම් යළි සකසා වරනය වන්නද\?</string>
<string name="require_restart_message">නව වෙනස්කම් භාවිතා කිරීමට යෙදුම නැවත පටන් ගන්න.</string>
<string name="watch_positions_summary">අවසාන වාදන ස්ථානයෙන් ඉදිරියට යන්න</string>
<string name="watch_positions">ස්ථානය මතක තබා ගන්න</string>
<string name="auth_instance_summary">සත්‍යතාව තහවුරු කිරීම සඳහා වෙනත් සේවාදායකයක් භාවිතා කරන්න.</string>
<string name="aspect_ratio">වීඩියෝ දර්ශන අනුපාතය</string>
<string name="auto_rotation">ස්වයංක්‍රීය භ්‍රමණය</string>
<string name="portrait">සිරස්</string>
<string name="always">සැමවිටම</string>
<string name="unlimited">අසීමිත</string>
<string name="no_player_found">බාහිර වාදකයක් හමු නොවීය. කරුණාකර ඔබ එකක් පිහිටුවා ඇති බවට වග බලා ගන්න.</string>
<string name="seekbar_preview">වීඩියෝ පෙරදසුන</string>
<string name="general">පොදු</string>
<string name="search_history_summary">සෙවුම් මතක තබා ගන්න</string>
<string name="captions">සිරස්තල</string>
<string name="none">කිසිවක් නැත</string>
<string name="system_caption_style">පද්ධති සිරස්තල විලාසය</string>
<string name="downloading_apk">APK බාගත කරමින්…</string>
<string name="notifications">දැනුම්දීම්</string>
<string name="default_subtitle_language">උපසිරැසි භාෂාව</string>
<string name="notify_new_streams_summary">ඔබ අනුගමනය කරන නිර්මාණකරුවන්ගෙන් නැවුම් අන්තර්ගත පිළිබඳ දැනුම්දීම්.</string>
<string name="history_empty">තවමත් ඉතිහාසයක් නැත.</string>
<string name="already_spent_time">ඔබ දැනටමත් යෙදුම තුළ මිනිත්තු %1$s ගත කර ඇත, විවේකයක් ගැනීමට කාලයයි.</string>
<string name="required_network">සම්බන්ධතාවයක් අවශ්‍ය</string>
<string name="downloadsucceeded">බාගත කිරීම සාර්ථක විය</string>
<string name="skip_buttons">මඟ හරින බොත්තම්</string>
<string name="new_videos_badge">නව වීඩියෝ සඳහා දර්ශකය</string>
<string name="save_feed_summary">නව දායකත්ව වීඩියෝ පසුබිමේ පූරණය කර එය ස්වයංක්‍රීයව නැවුම් වීම වළක්වන්න.</string>
<string name="resize_mode_zoom">විශාලනය කරන්න</string>
<string name="repeat_mode_none">කිසිවක් නැත</string>
<string name="add_to_queue">පෝලිමට එකතු කරන්න</string>
<string name="change_region">වත්මන් ප්‍රදේශය සඳහා නැගී එන වීඩියෝ නොමැති බව පෙනේ. කරුණාකර සැකසීම් තුළ වෙනත් එකක් තෝරන්න.</string>
<string name="player_resize_mode">ප්‍රමාණය වෙනස් කිරීමේ ප්‍රකාරය</string>
<string name="copied_to_clipboard">පසුරු පුවරුවට පිටපත් කර ඇත</string>
<string name="limit_hls">HLS 1080p දක්වා සීමා කරන්න</string>
<string name="legacy_subscriptions">උරුම දායකත්ව දසුන</string>
<string name="renamePlaylist">වාදන ලැයිස්තුව නැවත නම් කරන්න</string>
<string name="sb_skip_manual_summary">කොටස් ස්වයංක්‍රීයව මඟ නොහරින්න, සෑම විටම පෙර විමසන්න.</string>
<string name="default_load_interval">පෙරනිමිය</string>
<string name="playlists_order">වාදන ලැයිස්තු අනුපිළිවෙල</string>
<string name="time_code">කාල කේතය (තත්පර)</string>
<string name="sb_markers_summary">කාල තීරුවේ කොටස් සලකුණු කරන්න.</string>
<string name="playing_queue">වාදනය කරන පෝලිම</string>
<string name="confirm_unsubscribing_summary">දායකත්වයෙන් ඉවත් වීමට පෙර තහවුරු කිරීමේ සංවාදයක් පෙන්වන්න.</string>
<string name="playlistCloned">වාදන ලැයිස්තුව ක්ලෝන කරන ලදී</string>
<string name="alternative_player_layout">විකල්ප වාදක පිරිසැලසුම</string>
<string name="end_time">අවසන් කාලය</string>
<string name="start_time">ආරම්භක කාලය</string>
<string name="alternative_player_layout_summary">අදාළ වීඩියෝ පහතින් වෙනුවට අදහස්වලට ඉහළින් පේළියක් ලෙස පෙන්වන්න.</string>
<string name="hls_instead_of_dash_summary">DASH වෙනුවට HLS භාවිතා කරන්න (මන්දගාමී වනු ඇත, නිර්දේශ නොකරයි)</string>
<string name="bookmark">පොත් සලකුණ</string>
<string name="not_enabled">මෙනු අයිතමය සබල කර නැත!</string>
<string name="local_playlists">දේශීය වාදන ලැයිස්තු</string>
<string name="queue_insert_related_videos">අදාළ වීඩියෝ ඇතුළු කරන්න</string>
<string name="no_comments_available">මෙම වීඩියෝවට අදහස් නොමැත.</string>
<string name="comments_disabled">උඩුගත කරන්නා විසින් අදහස් අබල කර ඇත.</string>
<string name="double_tap_seek">අන්වේෂණයට දෙවරක් තට්ටු කරන්න</string>
<string name="double_tap_seek_summary">වාදකයේ ස්ථානය රිවයින්ඩ් කිරීමට හෝ ඉදිරියට යාමට වමේ හෝ දකුණේ දෙවරක් තට්ටු කරන්න.</string>
<string name="export_playlists">වාදන ලැයිස්තු අපනයනය කරන්න</string>
<string name="backup_restore_summary">දායකත්වයන්, වාදන ලැයිස්තු,… ආනයනය සහ අපනයනය කරන්න</string>
<string name="failed_fetching_instances">පවතින සේවාදායකයන් ලබා ගැනීමට නොහැකි විය.</string>
<string name="hide_watched_from_feed">නවතම කොටසෙහි නැරඹූ වීඩියෝ සඟවන්න</string>
<string name="areYouSure">මෙම වාදන ලැයිස්තුව මකන්නද\?</string>
<string name="emptyPlaylistName">වාදන ලැයිස්තුවේ නම හිස් විය නොහැක</string>
<string name="pauseOnScreenOff_summary">තිරය ක්‍රියාවිරහිත වූ විට වාදනය නවත්වන්න.</string>
<string name="seekbar_preview_summary">වාදන දර්ශකය ඇදගෙන යන විට සැණරුවක් පෙන්වන්න.</string>
<string name="playlistNameReversed">වාදන ලැයිස්තුවේ නම (ප්‍රතිලෝම)</string>
<string name="playlistCreated">වාදන ලැයිස්තුව සාදන ලදී.</string>
<string name="addToPlaylist">වාදන ලැයිස්තුවට එක් කරන්න</string>
<string name="background_channel_description">ශ්‍රව්‍ය ධාවකය පාලනය කිරීමට බොත්තම් සහිත දැනුම්දීමක් පෙන්වයි.</string>
<string name="remove_bookmark">පොත් සලකුණ ඉවත් කරන්න</string>
<string name="download_channel_name">බාගත කිරීමේ සේවාව</string>
<string name="push_channel_name">දැනුම්දීම් සේවකයා</string>
<string name="category_sponsor_description">ගෙවුම් ප්‍රවර්ධනය, ගෙවුම් යොමු කිරීම් සහ සෘජු දැන්වීම්. ස්වයං ප්‍රවර්ධනය හෝ නොමිලේ නිර්මාණකරුවන්, වෙබ් අඩවි සහ නිෂ්පාදන සඳහා සත්‍ය ප්‍රචාරක කටයුතු, සඳහා නොවේ.</string>
<string name="category_preview_description">මෙම හෝ එහි මාලාවේ අනාගත වීඩියෝවල ඉදිරියට එන අන්තර්ගතය විස්තර කරන කොටස් සඳහා, නමුත් අමතර තොරතුරු ලබා නොදේ. මෙහි පමණක් පෙනෙන ක්ලිප් ඇතුළත් නම්, මෙය බොහෝ දුරට වැරදි කාණ්ඩයකි.</string>
<string name="alternative_pip_controls_summary">ඉදිරියට සහ පසුපසට යැවීම වෙනුවට PiP හි ශබ්ද පමණක් සහ මඟහරින පාලක පෙන්වන්න</string>
</resources>

View File

@ -389,4 +389,61 @@
<string name="trending">Шта је сада у тренду</string>
<string name="bookmarks">Обележивачи</string>
<string name="bookmark">Обележивач</string>
<string name="local_playlists">Локалне листе</string>
<string name="color_violet">Свестрана љубичаста</string>
<string name="not_enabled">Ставка менија није омогућена!</string>
<string name="select_other_start_tab">Прво изаберите другу почетну картицу!</string>
<string name="brightness">Осветлење екрана</string>
<string name="volume">Јачина звука</string>
<string name="auto">Аутоматски</string>
<string name="swipe_controls">Брзо превлачење контрола</string>
<string name="swipe_controls_summary">Користите покрет брзог превлачења да бисте подесили светлину екрана и јачину звука.</string>
<string name="defaults">Подразумевано</string>
<string name="pop_up">Искачући</string>
<string name="captions_size">Величина натписа</string>
<string name="pause">Пауза</string>
<string name="rewind">Премотај уназад</string>
<string name="double_tap_seek">Додирните двапут да бисте тражили</string>
<string name="double_tap_seek_summary">Додирните двапут са леве или десне стране да бисте премотали или проследили позицију плејера.</string>
<string name="failed_fetching_instances">Није могуће добити доступне инстанце.</string>
<string name="hide_watched_from_feed_summary">Не приказуј видео записе који се гледају више од 90% на картици са праћењима.</string>
<string name="nothing_selected">Ништа изабрано!</string>
<string name="pinch_control">Штипање</string>
<string name="export_playlists">Извези листе</string>
<string name="app_backup">Прављење резервне копије апликације</string>
<string name="play_latest_videos">Репродукуј најновије видео записе</string>
<string name="audio_player">Аудио плејер</string>
<string name="playlistUrl">URL листе</string>
<string name="no_subtitle">Нема превода</string>
<string name="download_paused">Преузимање је паузирано</string>
<string name="download_completed">Преузимање је завршено</string>
<string name="concurrent_downloads">Максимално истовремених преузимања</string>
<string name="no_comments_available">Овај видео нема доступних коментара.</string>
<string name="theme_monochrome">Минималистички монохром</string>
<string name="all_caught_up_summary">Видели сте све нове снимке</string>
<string name="all_caught_up">Све прегледано</string>
<string name="import_playlists">Увези листе</string>
<string name="backup_restore_summary">Увоз &amp; извоз праћења, листа,…</string>
<string name="exportsuccess">Извезено.</string>
<string name="privacy_alert">Упозорење о приватности</string>
<string name="username_email">Желите ли да наставите са е-адресом која се не препоручује\?</string>
<string name="proceed">Настави</string>
<string name="hide_watched_from_feed">Сакриј гледане видео записе са фида</string>
<string name="forward">Премотај унапред</string>
<string name="alternative_pip_controls">Алтернативне PiP контроле</string>
<string name="alternative_pip_controls_summary">Приказивање само звука и прескакање контрола у PiP-у уместо унапред и уназад</string>
<string name="audio_only_mode">Режим само звука</string>
<string name="audio_only_mode_summary">Претвори LibreTube у музички плејер.</string>
<string name="concurrent_downloads_limit_reached">Максимално истовремених преузимања је достигнуто.</string>
<string name="pinch_control_summary">Користите покрет штипања за увећавање/умањивање.</string>
<string name="unknown">Непознато</string>
<string name="resume">Настави</string>
<string name="comments_disabled">Отпремилац је онемогућио коментаре.</string>
<string name="pause_on_quit">Пауза приликом изласка</string>
<string name="queue_insert_related_videos">Уметање сродних видео записа</string>
<string name="clear_bookmarks">Брисање обележивача</string>
<string name="bookmarks_empty">Нема обележивача!</string>
<string name="shuffle">Насумично</string>
<string name="remove_bookmark">Обриши обележивач</string>
<string name="add_to_bookmarks">Додај у обележиваче</string>
</resources>

View File

@ -437,4 +437,13 @@
<string name="alternative_pip_controls">Альтернативні елементи керування PiP</string>
<string name="alternative_pip_controls_summary">Показувати лише аудіо та пропускати елементи керування в PiP замість перемотування вперед та назад</string>
<string name="audio_player">Аудіопрогравач</string>
<string name="audio_only_mode">Режим лише аудіо</string>
<string name="audio_only_mode_summary">Перетворіть LibreTube на музичний програвач.</string>
<string name="no_subtitle">Субтитрів немає</string>
<string name="download_paused">Завантаження призупинено</string>
<string name="concurrent_downloads">Максимальна кількість одночасних завантажень</string>
<string name="concurrent_downloads_limit_reached">Досягнуто максимальної кількості одночасних завантажень.</string>
<string name="unknown">Невідомо</string>
<string name="download_completed">Завантаження завершено</string>
<string name="resume">Продовжити</string>
</resources>

View File

@ -83,4 +83,5 @@
<string name="music_songs">YT Music 歌</string>
<string name="sponsorblock_state"></string>
<string name="category_sponsor">Sponsor</string>
<string name="customization">較嘢</string>
</resources>

View File

@ -437,4 +437,13 @@
<string name="alternative_pip_controls">备选 PiP (画中画)操控</string>
<string name="alternative_pip_controls_summary">仅显示音频并跳过画中画操控而非前进和后退</string>
<string name="audio_player">音频播放器</string>
<string name="audio_only_mode_summary">将 LibreTube 作为音乐播放器。</string>
<string name="no_subtitle">无字幕</string>
<string name="download_paused">下载已暂停</string>
<string name="download_completed">下载已完成</string>
<string name="concurrent_downloads">最大并发下载</string>
<string name="concurrent_downloads_limit_reached">已达到最大并发下载限制。</string>
<string name="unknown">未知</string>
<string name="resume">恢复</string>
<string name="audio_only_mode">仅音频模式</string>
</resources>

View File

@ -28,7 +28,7 @@
<string-array name="languages">
<item>@string/systemLanguage</item>
<item>العربية</item>
<item>Azərbaycan dili</item>
<item>Azərbaycanca</item>
<item>Euskara</item>
<item>বাংলা</item>
<item>Català</item>

View File

@ -2,6 +2,7 @@
<color name="duration_background_color">#AA000000</color>
<color name="duration_text_color">#EEFFFFFF</color>
<color name="shortcut_color">#0061A6</color>
<color name="blue_md_theme_light_primary">#0058CB</color>
<color name="blue_md_theme_light_onPrimary">#FFFFFF</color>

View File

@ -440,6 +440,8 @@
<string name="audio_player">Audio player</string>
<string name="audio_only_mode">Audio only mode</string>
<string name="audio_only_mode_summary">Turn LibreTube into a music player.</string>
<string name="sleep_timer">Sleep timer</string>
<string name="skip_silence">Skip silence</string>
<!-- Notification channel strings -->
<string name="download_channel_name">Download Service</string>

View File

@ -50,14 +50,14 @@
<SwitchPreferenceCompat
android:defaultValue="false"
android:icon="@drawable/ic_notification"
app:key="break_reminder_toggle"
app:title="@string/break_reminder" />
app:key="sleep_timer_toggle"
app:title="@string/sleep_timer" />
<EditTextPreference
android:enabled="false"
android:icon="@drawable/ic_time"
app:defaultValue="60"
app:key="break_reminder"
app:key="sleep_timer_delay"
app:title="@string/break_reminder_time"
app:useSimpleSummaryProvider="true" />

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="25">
<shortcut
android:enabled="true"
android:icon="@drawable/ic_home"
android:shortcutId="home"
android:shortcutShortLabel="@string/startpage">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.github.libretube.ui.activities.MainActivity"
android:targetPackage="com.github.libretube" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@drawable/ic_trending"
android:shortcutId="trends"
android:shortcutShortLabel="@string/trends">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.github.libretube.ui.activities.MainActivity"
android:targetPackage="com.github.libretube" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@drawable/ic_subscriptions"
android:shortcutId="subscriptions"
android:shortcutShortLabel="@string/subscriptions">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.github.libretube.ui.activities.MainActivity"
android:targetPackage="com.github.libretube">
<extra
android:name="fragmentToOpen"
android:value="subscriptions" />
</intent>
</shortcut>
<shortcut
android:enabled="true"
android:icon="@drawable/ic_library"
android:shortcutId="library"
android:shortcutShortLabel="@string/library">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.github.libretube.ui.activities.MainActivity"
android:targetPackage="com.github.libretube">
<extra
android:name="fragmentToOpen"
android:value="library" />
</intent>
</shortcut>
</shortcuts>