Merge branch 'master' into LocalSubscriptionDao_suspend

# Conflicts:
#	app/src/main/java/com/github/libretube/util/BackupHelper.kt
This commit is contained in:
Isira Seneviratne 2023-01-24 04:40:59 +05:30
commit e238154217
22 changed files with 152 additions and 135 deletions

View File

@ -14,8 +14,8 @@ android {
applicationId 'com.github.libretube'
minSdk 21
targetSdk 33
versionCode 27
versionName '0.11.0'
versionCode 28
versionName '0.11.1'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
resValue "string", "app_name", "LibreTube"

View File

@ -26,7 +26,7 @@ object DatabaseHelper {
query {
Database.watchHistoryDao().insertAll(watchHistoryItem)
val maxHistorySize =
PreferenceHelper.getString(PreferenceKeys.WATCH_HISTORY_SIZE, "unlimited")
PreferenceHelper.getString(PreferenceKeys.WATCH_HISTORY_SIZE, "100")
if (maxHistorySize == "unlimited") return@query
// delete the first watch history entry if the limit is reached

View File

@ -3,8 +3,8 @@ package com.github.libretube.extensions
import android.content.res.Resources
/**
* Convert DP to pixels
* Convert dp to pixels
*/
fun Int.toPixel(): Float {
fun Int.dpToPx(): Float {
return this * Resources.getSystem().displayMetrics.density + 0.5f
}

View File

@ -11,12 +11,12 @@ import kotlinx.serialization.Serializable
@Serializable
data class BackupFile(
var watchHistory: List<WatchHistoryItem> = emptyList(),
var watchPositions: List<WatchPosition> = emptyList(),
var searchHistory: List<SearchHistoryItem> = emptyList(),
var localSubscriptions: List<LocalSubscription> = emptyList(),
var customInstances: List<CustomInstance> = emptyList(),
var playlistBookmarks: List<PlaylistBookmark> = emptyList(),
var localPlaylists: List<LocalPlaylistWithVideos> = emptyList(),
var preferences: List<PreferenceItem> = emptyList()
var watchHistory: List<WatchHistoryItem>? = emptyList(),
var watchPositions: List<WatchPosition>? = emptyList(),
var searchHistory: List<SearchHistoryItem>? = emptyList(),
var localSubscriptions: List<LocalSubscription>? = emptyList(),
var customInstances: List<CustomInstance>? = emptyList(),
var playlistBookmarks: List<PlaylistBookmark>? = emptyList(),
var localPlaylists: List<LocalPlaylistWithVideos>? = emptyList(),
var preferences: List<PreferenceItem>? = emptyList()
)

View File

@ -16,9 +16,9 @@ import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.AllCaughtUpRowBinding
import com.github.libretube.databinding.TrendingRowBinding
import com.github.libretube.databinding.VideoRowBinding
import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.formatShort
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toPixel
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.extensions.setFormattedDuration
import com.github.libretube.ui.extensions.setWatchProgressLength
@ -119,8 +119,8 @@ class VideosAdapter(
// set a fixed width for better visuals
val params = root.layoutParams
when (forceMode) {
ForceMode.RELATED -> params.width = (210).toPixel().toInt()
ForceMode.HOME -> params.width = (250).toPixel().toInt()
ForceMode.RELATED -> params.width = (210).dpToPx().toInt()
ForceMode.HOME -> params.width = (250).dpToPx().toInt()
else -> {}
}
root.layoutParams = params

View File

@ -21,6 +21,10 @@ class WatchHistoryAdapter(
) :
RecyclerView.Adapter<WatchHistoryViewHolder>() {
var visibleCount = minOf(10, watchHistory.size)
override fun getItemCount(): Int = visibleCount
fun removeFromWatchHistory(position: Int) {
val history = watchHistory[position]
query {
@ -31,6 +35,13 @@ class WatchHistoryAdapter(
notifyItemRangeChanged(position, itemCount)
}
fun showMoreItems() {
val oldSize = visibleCount
visibleCount += minOf(10, watchHistory.size - oldSize)
if (visibleCount == oldSize) return
notifyItemRangeInserted(oldSize, visibleCount)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WatchHistoryViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = VideoRowBinding.inflate(layoutInflater, parent, false)
@ -71,8 +82,4 @@ class WatchHistoryAdapter(
watchProgress.setWatchProgressLength(video.videoId, video.duration)
}
}
override fun getItemCount(): Int {
return watchHistory.size
}
}

View File

@ -91,9 +91,7 @@ class ChannelFragment : BaseFragment() {
binding.channelScrollView.viewTreeObserver
.addOnScrollChangedListener {
if (binding.channelScrollView.getChildAt(0).bottom
== (binding.channelScrollView.height + binding.channelScrollView.scrollY)
) {
if (!binding.channelScrollView.canScrollVertically(1)) {
try {
onScrollEnd.invoke()
} catch (e: Exception) {

View File

@ -18,7 +18,7 @@ import com.github.libretube.databinding.FragmentLibraryBinding
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.toPixel
import com.github.libretube.extensions.dpToPx
import com.github.libretube.ui.adapters.PlaylistBookmarkAdapter
import com.github.libretube.ui.adapters.PlaylistsAdapter
import com.github.libretube.ui.base.BaseFragment
@ -103,7 +103,7 @@ class LibraryFragment : BaseFragment() {
// optimize CreatePlaylistFab bottom margin if miniPlayer active
val bottomMargin = if (isMiniPlayerVisible) 64 else 16
val layoutParams = binding.createPlaylist.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.bottomMargin = bottomMargin.toPixel().toInt()
layoutParams.bottomMargin = bottomMargin.dpToPx().toInt()
binding.createPlaylist.layoutParams = layoutParams
}

View File

@ -184,6 +184,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
private var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled
val handler = Handler(Looper.getMainLooper())
private val mainActivity get() = activity as MainActivity
/**
* Receiver for all actions in the PiP mode
@ -283,7 +284,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
@SuppressLint("ClickableViewAccessibility")
private fun initializeTransitionLayout() {
val mainActivity = activity as MainActivity
mainActivity.binding.container.visibility = View.VISIBLE
val mainMotionLayout = mainActivity.binding.mainMotionLayout
@ -360,16 +360,12 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.autoPlay.visibility = View.VISIBLE
binding.playImageView.setOnClickListener {
if (!exoPlayer.isPlaying) {
// start or go on playing
if (exoPlayer.playbackState == Player.STATE_ENDED) {
// restart video if finished
when {
!exoPlayer.isPlaying && exoPlayer.playbackState == Player.STATE_ENDED -> {
exoPlayer.seekTo(0)
}
exoPlayer.play()
} else {
// pause the video
exoPlayer.pause()
!exoPlayer.isPlaying -> exoPlayer.play()
else -> exoPlayer.pause()
}
}
@ -498,7 +494,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen_exit)
playerBinding.exoTitle.visibility = View.VISIBLE
val mainActivity = activity as MainActivity
if (!PlayerHelper.autoRotationEnabled) {
// different orientations of the video are only available when auto rotation is disabled
val orientation = PlayerHelper.getOrientation(exoPlayer.videoSize)
@ -523,7 +518,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
if (!PlayerHelper.autoRotationEnabled) {
// switch back to portrait mode if auto rotation disabled
val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
}
@ -550,7 +544,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
binding.playerViewsInfo.text = viewInfo
if (this::chapters.isInitialized && chapters.isNotEmpty()) {
val chapterIndex = getCurrentChapterIndex()
val chapterIndex = getCurrentChapterIndex() ?: return
// scroll to the current chapter in the chapterRecView in the description
val layoutManager = binding.chaptersRecView.layoutManager as LinearLayoutManager
layoutManager.scrollToPositionWithOffset(chapterIndex, 0)
@ -1165,19 +1159,14 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
"(${chapter.start?.let { DateUtils.formatElapsedTime(it) }}) ${chapter.title}"
}
playerBinding.chapterLL.setOnClickListener {
if (viewModel.isFullscreen.value!!) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.chapters)
.setItems(titles.toTypedArray()) { _, index ->
exoPlayer.seekTo(
chapters[index].start!! * 1000
)
}
.show()
} else {
toggleDescription()
}
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.chapters)
.setItems(titles.toTypedArray()) { _, index ->
exoPlayer.seekTo(chapters[index].start!! * 1000)
}
.show()
}
setCurrentChapterName()
}
@ -1189,7 +1178,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
// call the function again in 100ms
exoPlayerView.postDelayed(this::setCurrentChapterName, 100)
val chapterIndex = getCurrentChapterIndex()
val chapterIndex = getCurrentChapterIndex() ?: return
val chapterName = chapters[chapterIndex].title?.trim()
// change the chapter name textView text to the chapterName
@ -1204,18 +1193,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
/**
* Get the name of the currently played chapter
*/
private fun getCurrentChapterIndex(): Int {
val currentPosition = exoPlayer.currentPosition
var chapterIndex = 0
chapters.forEachIndexed { index, chapter ->
// check whether the chapter start is greater than the current player position
if (currentPosition >= chapter.start!! * 1000) {
// save chapter title if found
chapterIndex = index
}
}
return chapterIndex
private fun getCurrentChapterIndex(): Int? {
val currentPosition = exoPlayer.currentPosition / 1000
return chapters.indexOfFirst { currentPosition >= it.start!! }.takeIf { it >= 0 }
}
private fun setMediaSource(uri: Uri, mimeType: String) {
@ -1356,7 +1336,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
*/
@SuppressLint("SourceLockedOrientationActivity")
private fun changeOrientationMode() {
val mainActivity = activity as MainActivity
if (PlayerHelper.autoRotationEnabled) {
// enable auto rotation
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
@ -1527,7 +1506,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
private fun killPlayerFragment() {
viewModel.isFullscreen.value = false
binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity
mainActivity.supportFragmentManager.beginTransaction()
.remove(this)
.commit()

View File

@ -22,9 +22,9 @@ import com.github.libretube.db.DatabaseHolder
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.query
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toPixel
import com.github.libretube.extensions.toPlaylistBookmark
import com.github.libretube.ui.adapters.PlaylistAdapter
import com.github.libretube.ui.base.BaseFragment
@ -86,7 +86,7 @@ class PlaylistFragment : BaseFragment() {
playerViewModel.isMiniPlayerVisible.observe(viewLifecycleOwner) {
binding.playlistRecView.updatePadding(
bottom = if (it) (64).toPixel().toInt() else 0
bottom = if (it) (64).dpToPx().toInt() else 0
)
}
@ -212,9 +212,7 @@ class PlaylistFragment : BaseFragment() {
binding.playlistRecView.adapter = playlistAdapter
binding.playlistScrollview.viewTreeObserver
.addOnScrollChangedListener {
if (binding.playlistScrollview.getChildAt(0).bottom
== (binding.playlistScrollview.height + binding.playlistScrollview.scrollY)
) {
if (!binding.playlistScrollview.canScrollVertically(1)) {
if (isLoading) return@addOnScrollChangedListener
// append more playlists to the recycler view

View File

@ -137,9 +137,7 @@ class SubscriptionsFragment : BaseFragment() {
binding.scrollviewSub.viewTreeObserver
.addOnScrollChangedListener {
if (binding.scrollviewSub.getChildAt(0).bottom
== (binding.scrollviewSub.height + binding.scrollviewSub.scrollY)
) {
if (!binding.scrollviewSub.canScrollVertically(1)) {
// scroll view is at bottom
if (viewModel.videoFeed.value == null) return@addOnScrollChangedListener
binding.subRefresh.isRefreshing = true

View File

@ -1,6 +1,8 @@
package com.github.libretube.ui.fragments
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -14,8 +16,8 @@ import com.github.libretube.api.obj.StreamItem
import com.github.libretube.databinding.FragmentWatchHistoryBinding
import com.github.libretube.db.DatabaseHolder.Companion.Database
import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.query
import com.github.libretube.extensions.toPixel
import com.github.libretube.ui.adapters.WatchHistoryAdapter
import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.models.PlayerViewModel
@ -28,6 +30,7 @@ class WatchHistoryFragment : BaseFragment() {
private lateinit var binding: FragmentWatchHistoryBinding
private val playerViewModel: PlayerViewModel by activityViewModels()
private var isLoading = false
override fun onCreateView(
inflater: LayoutInflater,
@ -43,13 +46,13 @@ class WatchHistoryFragment : BaseFragment() {
playerViewModel.isMiniPlayerVisible.observe(viewLifecycleOwner) {
binding.watchHistoryRecView.updatePadding(
bottom = if (it) (64).toPixel().toInt() else 0
bottom = if (it) (64).dpToPx().toInt() else 0
)
}
val watchHistory = awaitQuery {
Database.watchHistoryDao().getAll()
}
}.reversed()
if (watchHistory.isEmpty()) return
@ -96,16 +99,15 @@ class WatchHistoryFragment : BaseFragment() {
)
}
// reversed order
binding.watchHistoryRecView.layoutManager = LinearLayoutManager(requireContext()).apply {
reverseLayout = true
stackFromEnd = true
}
val watchHistoryAdapter = WatchHistoryAdapter(
watchHistory.toMutableList()
)
binding.watchHistoryRecView.layoutManager = LinearLayoutManager(context)
binding.watchHistoryRecView.adapter = watchHistoryAdapter
binding.historyEmpty.visibility = View.GONE
binding.historyScrollView.visibility = View.VISIBLE
val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(
0,
ItemTouchHelper.LEFT
@ -130,7 +132,7 @@ class WatchHistoryFragment : BaseFragment() {
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
itemTouchHelper.attachToRecyclerView(binding.watchHistoryRecView)
// observe changes
// observe changes to indicate if the history is empty
watchHistoryAdapter.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
@ -141,8 +143,15 @@ class WatchHistoryFragment : BaseFragment() {
}
})
binding.watchHistoryRecView.adapter = watchHistoryAdapter
binding.historyEmpty.visibility = View.GONE
binding.historyScrollView.visibility = View.VISIBLE
// add a listener for scroll end, delay needed to prevent loading new ones the first time
Handler(Looper.getMainLooper()).postDelayed({
binding.historyScrollView.viewTreeObserver.addOnScrollChangedListener {
if (!binding.historyScrollView.canScrollVertically(1) && !isLoading) {
isLoading = true
watchHistoryAdapter.showMoreItems()
isLoading = false
}
}
}, 200)
}
}

View File

@ -9,7 +9,7 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R
import com.github.libretube.databinding.CommentsSheetBinding
import com.github.libretube.extensions.toPixel
import com.github.libretube.extensions.dpToPx
import com.github.libretube.ui.adapters.CommentsAdapter
import com.github.libretube.ui.models.CommentsViewModel
@ -37,7 +37,7 @@ class CommentsSheet : ExpandedBottomSheet() {
binding.dragHandle.viewTreeObserver.removeOnGlobalLayoutListener(this)
// limit the recyclerview height to not cover the video
binding.commentsRV.layoutParams = binding.commentsRV.layoutParams.apply {
height = viewModel.maxHeight - (binding.dragHandle.height + (20).toPixel().toInt())
height = viewModel.maxHeight - (binding.dragHandle.height + (20).dpToPx().toInt())
}
}
})

View File

@ -19,8 +19,8 @@ import com.github.libretube.R
import com.github.libretube.databinding.DoubleTapOverlayBinding
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.github.libretube.databinding.PlayerGestureControlsViewBinding
import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.normalize
import com.github.libretube.extensions.toPixel
import com.github.libretube.obj.BottomSheetItem
import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.base.BaseActivity
@ -573,8 +573,8 @@ internal class CustomExoPlayerView(
// add a larger bottom margin to the time bar in landscape mode
val offset = when (newConfig?.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> 20.toPixel()
else -> 10.toPixel()
Configuration.ORIENTATION_LANDSCAPE -> 20.dpToPx()
else -> 10.dpToPx()
}
binding.progressBar.let {
@ -624,7 +624,7 @@ internal class CustomExoPlayerView(
playerViewModel?.isFullscreen?.value == true
binding.topBar.let {
it.layoutParams = (it.layoutParams as MarginLayoutParams).apply {
topMargin = (if (isFullscreen) 25 else 5).toPixel().toInt()
topMargin = (if (isFullscreen) 10 else 0).dpToPx().toInt()
}
}
}
@ -719,6 +719,6 @@ internal class CustomExoPlayerView(
private const val SUBTITLE_BOTTOM_PADDING_FRACTION = 0.158f
private const val ANIMATION_DURATION = 100L
private const val AUTO_HIDE_CONTROLLER_DELAY = 2000L
private val LANDSCAPE_MARGIN_HORIZONTAL = (20).toPixel().toInt()
private val LANDSCAPE_MARGIN_HORIZONTAL = (20).dpToPx().toInt()
}
}

View File

@ -10,7 +10,7 @@ import androidx.core.view.marginLeft
import com.github.libretube.R
import com.github.libretube.api.obj.Segment
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.extensions.toPixel
import com.github.libretube.extensions.dpToPx
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.ThemeHelper
import com.google.android.exoplayer2.Player
@ -28,7 +28,7 @@ class MarkableTimeBar(
private var player: Player? = null
private var length: Int = 0
private val progressBarHeight = (2).toPixel().toInt()
private val progressBarHeight = (2).dpToPx().toInt()
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)

View File

@ -51,23 +51,23 @@ class BackupHelper(private val context: Context) {
runBlocking(Dispatchers.IO) {
Database.watchHistoryDao().insertAll(
*backupFile.watchHistory.toTypedArray()
*backupFile.watchHistory.orEmpty().toTypedArray()
)
Database.searchHistoryDao().insertAll(
*backupFile.searchHistory.toTypedArray()
*backupFile.searchHistory.orEmpty().toTypedArray()
)
Database.watchPositionDao().insertAll(
*backupFile.watchPositions.toTypedArray()
*backupFile.watchPositions.orEmpty().toTypedArray()
)
Database.localSubscriptionDao().insertAll(backupFile.localSubscriptions)
Database.localSubscriptionDao().insertAll(backupFile.localSubscriptions.orEmpty())
Database.customInstanceDao().insertAll(
*backupFile.customInstances.toTypedArray()
*backupFile.customInstances.orEmpty().toTypedArray()
)
Database.playlistBookmarkDao().insertAll(
*backupFile.playlistBookmarks.toTypedArray()
*backupFile.playlistBookmarks.orEmpty().toTypedArray()
)
backupFile.localPlaylists.forEach {
backupFile.localPlaylists.orEmpty().forEach {
Database.localPlaylistsDao().createPlaylist(it.playlist)
val playlistId = Database.localPlaylistsDao().getAll().last().playlist.id
it.videos.forEach {

View File

@ -8,8 +8,8 @@
<string name="login">ورود</string>
<string name="logout">خروج</string>
<string name="cancel">لغو</string>
<string name="loggedIn">با موفقیت وارد شدید!</string>
<string name="loggedout">با موفقیت خارج شدید!</string>
<string name="loggedIn">وارد شدید.</string>
<string name="loggedout">خارج شدید.</string>
<string name="yes">بله</string>
<string name="unsubscribe">لغو اشتراک</string>
<string name="search_hint">جستجو</string>
@ -17,31 +17,31 @@
<string name="download">دانلود</string>
<string name="register">ثبت نام</string>
<string name="customInstance">سفارشی</string>
<string name="please_login">لطفا ابتدا در تنظیمات وارد شوید یا ثبت نام کنید!</string>
<string name="importsuccess">با موفقیت مشترک شدید!</string>
<string name="please_login">لطفا ابتدا در بخش تنظیمات وارد حساب خود شده یا ثبت نام کنید.</string>
<string name="importsuccess">مشترک شدید.</string>
<string name="subscribeIsEmpty">ابتدا در برخی از کانال‌ها مشترک شوید!</string>
<string name="dlcomplete">دانلود کامل شد!</string>
<string name="downloadfailed">دانلود شکست خورد!</string>
<string name="downloadfailed">دانلود ناموفق‎.</string>
<string name="vlc">باز کردن در VLC</string>
<string name="unknown_error">خطای شبکه!</string>
<string name="instances">انتخاب کنید …</string>
<string name="login_first">لطفا وارد شوید و دوباره امتحان کنید!</string>
<string name="login_first">لطفا وارد شوید و دوباره امتحان کنید.</string>
<string name="region">منطقه</string>
<string name="login_register">ورود/ثبت‌نام</string>
<string name="app_theme">پوسته</string>
<string name="import_from_yt">وارد کردن اشتراک‌ها</string>
<string name="registered">ثبت نام با موفقیت انجام شد! حالا می‌توانید چنل مورد نظر خودتان را دنبال کنید.</string>
<string name="already_logged_in">شما در حال حاضر وارد حساب‌تان شدید، می‌توانید از حساب‌تان خارج شوید.</string>
<string name="dlisinprogress">دانلود دیگری در جریان است، لطفا صبر کنید تا دانلود کامل شود!</string>
<string name="cannotDownload">این ویدئو را نمی‌توان دانلود کرد!</string>
<string name="registered">ثبت نام انجام شد. حالا می‌توانید کانالهای مورد نظر خودتان را دنبال کنید.</string>
<string name="already_logged_in">در حال حاضر وارد حساب‌تان هستید، ممکن است از حساب‌تان خارج شوید.</string>
<string name="dlisinprogress">دانلود دیگری در جریان است، لطفا صبر کنید تا دانلود تمام شود.</string>
<string name="cannotDownload">این ویدئو را نمی‌توان دانلود کرد.</string>
<string name="server_error">سرور دچار مشکل شده. شاید بهترست نمونه دیگری را امتحان کنید؟</string>
<string name="error">مشکلی پیش آمده است!</string>
<string name="notgmail">این مربوط به حساب پایپ است</string>
<string name="defres">ابعاد ویدئو</string>
<string name="empty">نام‌کاربری و رمز‌عبور نمی‌تواند خالی باشد!</string>
<string name="emptyList">هیچی اینجا نیست!</string>
<string name="vlcerror">نمی توانید در VLC ویدئو را ببینید. لطفا مطمئن شوید که برنامه VLC را نصب کرده باشید</string>
<string name="grid">انتخاب تعداد ستون ها</string>
<string name="vlcerror">نمی توانید در VLC ویدئو را ببینید. ممکن است برنامه VLC نصب نباشد.</string>
<string name="grid">انتخاب تعداد ستونها</string>
<string name="deletePlaylist">حذف لیست پخش</string>
<string name="areYouSure">لیست پخش حذف شود؟</string>
<string name="createPlaylist">ساخت لیست پخش</string>
@ -49,18 +49,18 @@
<string name="playlistName">نام فهرست پخش</string>
<string name="addToPlaylist">افزودن به فهرست پخش</string>
<string name="success">انجام شد.</string>
<string name="fail">ناموفق :(</string>
<string name="fail">ناموفق بود :(</string>
<string name="about">درباره ما</string>
<string name="emptyPlaylistName">نام فهرست پخش نمی‌تواند خالی باشه</string>
<string name="emptyPlaylistName">نام لیست پخش نمی‌تواند خالی باشد</string>
<string name="import_from_yt_summary">از یوتوب یا نیوپایپ</string>
<string name="startpage">خانه</string>
<string name="library">کتابخانه</string>
<string name="changeLanguage">زبان</string>
<string name="systemDefault">پیشفرض سیستم</string>
<string name="systemDefault">سیستم</string>
<string name="lightTheme">پوسته روشن</string>
<string name="darkTheme">پوسته تیره</string>
<string name="subscribers">%1$s مشترکین</string>
<string name="videos">ویدیو ها</string>
<string name="videos">ویدیوها</string>
<string name="subscriptions">اشتراک‌ها</string>
<string name="systemLanguage">زبان سیستم</string>
<string name="history">تاریخچه</string>
@ -73,11 +73,11 @@
<string name="instance">نمونه</string>
<string name="customization">تنظیمات</string>
<string name="website">وب‌سایت</string>
<string name="videoCount">%1$s ویدیوها</string>
<string name="videoCount">%1$s ویدیو</string>
<string name="noInternet">ابتدا به اینترنت متصل شوید</string>
<string name="retry">تلاش مجدد</string>
<string name="retry">تلاش دوباره</string>
<string name="comments">نظرات</string>
<string name="defaultTab">زبانه پیش فرض</string>
<string name="defaultTab">زبانه پیشفرض</string>
<string name="sponsorblock">اسپانسربلاک</string>
<string name="sponsorblock_state">فعال</string>
<string name="category_sponsor">اسپانسر</string>
@ -92,7 +92,7 @@
<string name="category_interaction">یادآوری تعامل (لایک و اشتراک)</string>
<string name="color_accent">لهجه</string>
<string name="color_blue">ابی شاد</string>
<string name="color_green">سبز</string>
<string name="color_green">سبز دره‌‌‌ها</string>
<string name="color_yellow">زرد</string>
<string name="color_purple">بنفش</string>
<string name="oledTheme">سیاه</string>
@ -101,11 +101,11 @@
<string name="disabled">خاموش</string>
<string name="appearance">ظاهر</string>
<string name="app_behavior">رفتار</string>
<string name="download_directory">دانلود در...</string>
<string name="download_directory">دانلود در</string>
<string name="download_directory_summary">جایی که فایل دانلود شده ذخیره میشود.</string>
<string name="update_available">نسخه %1$s موجود است</string>
<string name="downloads">دانلودها</string>
<string name="video_format">فرمت ویدئو</string>
<string name="video_format">فرمت ویدیو</string>
<string name="contributing">کمک کردن</string>
<string name="choose_filter">انتخاب فیلتر جستجو</string>
<string name="music_songs">آهنگ های موسیقی یوتیوب</string>
@ -126,4 +126,9 @@
<string name="advanced">پیشرفته</string>
<string name="player">پخش کننده</string>
<string name="live">زنده</string>
<string name="sponsorblock_notifications">آگاه‌سازها</string>
<string name="category_selfpromo">بدون حقوق/تبلیغ خود</string>
<string name="app_icon">آیکون</string>
<string name="no_update_available">شما در حال اجرای جدیدترین نسخه هستید.</string>
<string name="update_summary">بررسی برای بروزرسانی</string>
</resources>

View File

@ -437,4 +437,17 @@
<string name="alternative_pip_controls">Contrôles alternatifs de la PiP</string>
<string name="alternative_pip_controls_summary">Afficher uniquement l\'audio et les commandes de saut dans PiP au lieu de l\'avance et du retour en arrière</string>
<string name="audio_player">Lecteur audio</string>
<string name="faq">FAQ</string>
<string name="concurrent_downloads">Maximum de téléchargements simultanés</string>
<string name="skip_silence">Saute les silences</string>
<string name="concurrent_downloads_limit_reached">Limite maximum de téléchargements simultanés atteinte.</string>
<string name="unknown">Inconnu</string>
<string name="resume">Reprendre</string>
<string name="help">Aide</string>
<string name="audio_only_mode">Mode audio seulement</string>
<string name="audio_only_mode_summary">Transforme LibreTube en lecteur de musique.</string>
<string name="no_subtitle">Pas de sous-titre</string>
<string name="download_paused">Téléchargement en pause</string>
<string name="download_completed">Téléchargement terminé</string>
<string name="sleep_timer">Minuteur de sommeil</string>
</resources>

View File

@ -346,4 +346,7 @@
<string name="save_feed">Ielādēt plūsmu fonā</string>
<string name="save_feed_summary">Ielādē abonementu plūsmu fonā un neļauj tai automātiski atjaunoties.</string>
<string name="play_next">Atskaņot nākamo</string>
<string name="no_subtitle">Bez subtitriem</string>
<string name="download_paused">Lejupielāde iepauzēta</string>
<string name="download_completed">Lejupielāde pabeigta</string>
</resources>

View File

@ -177,7 +177,7 @@
<string name="advanced_summary">Transferências e redefinição</string>
<string name="player_summary">Padrões e comportamento</string>
<string name="hide_chapters">Ocultar capítulos</string>
<string name="related_streams_summary">Exibir transmissões semelhantes ao que você assiste.</string>
<string name="related_streams_summary">Exibir conteúdo semelhante ao que você assiste.</string>
<string name="open">Abrir…</string>
<string name="reset">Restaurar para os padrões</string>
<string name="default_subtitle_language">Idioma de legendas</string>
@ -207,7 +207,7 @@
<string name="watch_history_summary">Acompanhar os vídeos assistidos localmente</string>
<string name="reset_watch_positions">Redefinir</string>
<string name="autoRotatePlayer">Tela cheia automática</string>
<string name="notify_new_streams">Notificações de novas transmissões</string>
<string name="notify_new_streams">Notificações de novos vídeos</string>
<string name="most_views">Mais visualizações</string>
<string name="network_wifi">Só no Wi-Fi</string>
<string name="translate">Tradução</string>
@ -435,18 +435,18 @@
<string name="forward">Avançar</string>
<string name="pause">Pausar</string>
<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="alternative_pip_controls_summary">Mostrar opções de apenas áudio e pular no PiP em vez de avançar e retroceder</string>
<string name="audio_player">Reprodutor de áudio</string>
<string name="no_subtitle">Sem legendas</string>
<string name="download_paused">Transferência pausada</string>
<string name="download_completed">Transferência concluída</string>
<string name="concurrent_downloads">Número de transferências simultâneas</string>
<string name="concurrent_downloads_limit_reached">Limite máximo de transferências simultâneas 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="audio_only_mode">Modo apenas áudio</string>
<string name="sleep_timer">Temporizador de sono</string>
<string name="skip_silence">Pular silêncio</string>
<string name="help">Ajuda</string>
<string name="faq">FAQ</string>

View File

@ -28,7 +28,7 @@
app:title="@string/watch_history" />
<ListPreference
android:defaultValue="unlimited"
android:defaultValue="100"
android:entries="@array/historySize"
android:entryValues="@array/historySizeValues"
android:icon="@drawable/ic_list"

View File

@ -0,0 +1,8 @@
* Fix channel crashes
* Fix backup backwards compatibility
* Fix unresponsiveness when opening watch history
* Fix that clicking description links plays next video
* Keep the screen on while playing in the offline player
* [Audio mode] Show video options when clicking thumbnail
* Reduce the player top bar margin
* Minor player view improvements