mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-08-02 04:43:17 +00:00
Compare commits
1 commit
master
...
fixsubtitl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9964f6fbf0 |
34 changed files with 140 additions and 658 deletions
|
|
@ -701,23 +701,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(
|
|||
homeViewModel.queryTextSubmit("")
|
||||
}
|
||||
|
||||
// Load value for toggling Tv layout real time clock. Hide by default at startup
|
||||
// set visibility first, to apply a scroll effect later
|
||||
context?.let {
|
||||
if (isLayout(TV)) {
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
|
||||
val toggleClock =
|
||||
settingsManager.getBoolean(
|
||||
getString(R.string.tv_layout_clock_key),
|
||||
false
|
||||
)
|
||||
binding.homeClock.isVisible = toggleClock
|
||||
} else {
|
||||
binding.homeClock.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
if (isLayout(PHONE)) {
|
||||
|
|
@ -757,17 +740,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(
|
|||
view.getLocationInWindow(rect)
|
||||
scrollParent.isVisible = true
|
||||
scrollParent.translationY = rect[1].toFloat() - 60.toPx
|
||||
|
||||
// Move the TV layout real time clock out of the way too
|
||||
// We check if we have the correct layout and if the clock is enabled
|
||||
if(isLayout(TV) && binding.homeClock.isVisible) {
|
||||
val scrollParent = binding.homeClock
|
||||
|
||||
val rect = IntArray(2)
|
||||
view.getLocationInWindow(rect)
|
||||
scrollParent.isVisible = true
|
||||
scrollParent.translationY = rect[1].toFloat() - 60.toPx
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
|
|
|||
|
|
@ -307,7 +307,6 @@ open class FullScreenPlayer : AbstractPlayerFragment<FragmentPlayerBinding>(
|
|||
playerVideoTitleRez,
|
||||
playerVideoInfo,
|
||||
playerGoBackHolder,
|
||||
playerVideoClock,
|
||||
).forEach {
|
||||
it.animateY(titleMove)
|
||||
}
|
||||
|
|
@ -773,7 +772,7 @@ open class FullScreenPlayer : AbstractPlayerFragment<FragmentPlayerBinding>(
|
|||
val showPlayerEpisodes = !isGone && isThereEpisodes()
|
||||
playerEpisodesButtonRoot.isVisible = showPlayerEpisodes
|
||||
playerEpisodesButton.isVisible = showPlayerEpisodes
|
||||
playerVideoTitleHolder.isGone = togglePlayerTitleGone || playerVideoTitle.text.isBlank()
|
||||
playerVideoTitleHolder.isGone = togglePlayerTitleGone
|
||||
playerVideoTitleRez.isGone = isGone || playerVideoTitleRez.text.isBlank()
|
||||
playerEpisodeFiller.isGone = isGone
|
||||
playerCenterMenu.isGone = isGone
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Spanned
|
||||
|
|
@ -80,7 +79,6 @@ import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioT
|
|||
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.LinkSource
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
||||
|
|
@ -134,7 +132,6 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.Serializable
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Calendar
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
|
@ -1115,29 +1112,21 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
var sourceIndex = 0
|
||||
var startSource = 0
|
||||
// Filtered and sorted links
|
||||
var currentHiddenFooter: View? = null
|
||||
var filteredLinks: List<DisplayLink> = emptyList()
|
||||
var sortedUrls = emptyList<Pair<ExtractorLink?, ExtractorUri?>>()
|
||||
|
||||
fun refreshLinks(qualityProfile: Int) {
|
||||
val currentLinkUsed = currentSelectedLink
|
||||
// Always display current linkFooter
|
||||
val sortedLinks = viewModel.state.sortLinks(qualityProfile)
|
||||
|
||||
filteredLinks = sortedLinks.filter { it.shouldUseLink || it.link == currentLinkUsed }
|
||||
|
||||
if (sortedLinks.isEmpty()) {
|
||||
sortedUrls = viewModel.state.sortLinks(qualityProfile)
|
||||
if (sortedUrls.isEmpty()) {
|
||||
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone =
|
||||
true
|
||||
} else {
|
||||
startSource = filteredLinks.indexOfFirst { it.link == currentLinkUsed }
|
||||
startSource = sortedUrls.indexOf(currentSelectedLink)
|
||||
sourceIndex = startSource
|
||||
|
||||
val sourcesArrayAdapter =
|
||||
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||
|
||||
sourcesArrayAdapter.addAll(filteredLinks.map { displayLink ->
|
||||
val (link, uri) = displayLink.link
|
||||
sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) ->
|
||||
val name = link?.name ?: uri?.name ?: "NULL"
|
||||
"$name ${Qualities.getStringByInt(link?.quality)}"
|
||||
})
|
||||
|
|
@ -1153,7 +1142,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
providerList.setOnItemLongClickListener { _, _, position, _ ->
|
||||
sortedLinks.getOrNull(position)?.link?.first?.url?.let {
|
||||
sortedUrls.getOrNull(position)?.first?.url?.let {
|
||||
clipboardHelper(
|
||||
txt(R.string.video_source),
|
||||
it
|
||||
|
|
@ -1161,25 +1150,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
true
|
||||
}
|
||||
|
||||
val hiddenLinks = sortedLinks.size - filteredLinks.size
|
||||
providerList.removeFooterView(currentHiddenFooter)
|
||||
|
||||
if (hiddenLinks > 0) {
|
||||
val hiddenLinksFooter: TextView = layoutInflater.inflate(
|
||||
R.layout.sort_bottom_footer_add_choice, null
|
||||
) as TextView
|
||||
|
||||
providerList.addFooterView(hiddenLinksFooter, null, false)
|
||||
currentHiddenFooter = hiddenLinksFooter
|
||||
|
||||
val hiddenLinksText =
|
||||
ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks)
|
||||
.format(hiddenLinks)
|
||||
hiddenLinksFooter.text = hiddenLinksText
|
||||
hiddenLinksFooter.setCompoundDrawables(null, null, null, null)
|
||||
hiddenLinksFooter.setTypeface(null, Typeface.ITALIC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1393,8 +1363,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
if (init) {
|
||||
filteredLinks.getOrNull(sourceIndex)?.let {
|
||||
loadLink(it.link, true)
|
||||
sortedUrls.getOrNull(sourceIndex)?.let {
|
||||
loadLink(it, true)
|
||||
}
|
||||
}
|
||||
sourceDialog.dismissSafe(activity)
|
||||
|
|
@ -1562,10 +1532,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
override fun playerError(exception: Throwable) {
|
||||
currentSelectedLink?.let { link ->
|
||||
viewModel.modifyState { this.addError(link) }
|
||||
}
|
||||
|
||||
val currentUrl =
|
||||
currentSelectedLink?.let { it.first?.url ?: it.second?.uri?.toString() } ?: "unknown"
|
||||
val headers = currentSelectedLink?.first?.headers?.toString() ?: "none"
|
||||
|
|
@ -1588,22 +1554,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
private fun noLinksFound() {
|
||||
viewModel.forceClearCache = true
|
||||
val hiddenLinks = viewModel.state.sortLinks(currentQualityProfile).count { !it.shouldUseLink }
|
||||
|
||||
context?.let { ctx ->
|
||||
// Display that there are hidden links to the user.
|
||||
if (hiddenLinks > 0) {
|
||||
val noLinksString = ctx.getString(R.string.no_links_found_toast)
|
||||
val hiddenString =
|
||||
ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks)
|
||||
.format(hiddenLinks)
|
||||
val toastText = "$noLinksString\n($hiddenString)"
|
||||
showToast(toastText, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
activity?.popCurrentPage()
|
||||
}
|
||||
|
||||
|
|
@ -1614,9 +1566,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
val links = viewModel.state.sortLinks(currentQualityProfile)
|
||||
|
||||
val firstAvailableLink = links.firstOrNull { it.shouldUseLink }?.link
|
||||
if (firstAvailableLink == null) {
|
||||
if (links.isEmpty()) {
|
||||
noLinksFound()
|
||||
return
|
||||
}
|
||||
|
|
@ -1624,7 +1574,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
if (!isPlayerActive.compareAndSet(false, true)) {
|
||||
return
|
||||
}
|
||||
loadLink(firstAvailableLink, false)
|
||||
loadLink(links.first(), false)
|
||||
showPlayerMetadata()
|
||||
}
|
||||
|
||||
|
|
@ -1691,26 +1641,25 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getNextLink(): DisplayLink? {
|
||||
val links = viewModel.state.sortLinks(currentQualityProfile)
|
||||
val currentIndex = links.indexOfFirst { it.link == currentSelectedLink }
|
||||
val nextPotentialLink =
|
||||
links.withIndex().firstOrNull { it.index > currentIndex && it.value.shouldUseLink }
|
||||
return nextPotentialLink?.value
|
||||
}
|
||||
|
||||
override fun hasNextMirror(): Boolean {
|
||||
return getNextLink() != null
|
||||
val links = viewModel.state.sortLinks(currentQualityProfile)
|
||||
return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size
|
||||
}
|
||||
|
||||
override fun nextMirror() {
|
||||
val nextLink = getNextLink()
|
||||
if (nextLink == null) {
|
||||
val links = viewModel.state.sortLinks(currentQualityProfile)
|
||||
if (links.isEmpty()) {
|
||||
noLinksFound()
|
||||
return
|
||||
}
|
||||
|
||||
loadLink(nextLink.link, true)
|
||||
val newIndex = links.indexOf(currentSelectedLink) + 1
|
||||
if (newIndex >= links.size) {
|
||||
noLinksFound()
|
||||
return
|
||||
}
|
||||
|
||||
loadLink(links[newIndex], true)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
@ -2217,7 +2166,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
isPlayerActive.set(false)
|
||||
binding?.overlayLoadingSkipButton?.isVisible = false
|
||||
binding?.playerLoadingOverlay?.isVisible = true
|
||||
viewModel.modifyState { setError(emptyList()) }
|
||||
uiReset()
|
||||
}
|
||||
|
||||
|
|
@ -2271,14 +2219,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
fromTagToEnglishLanguageName(it)?.lowercase() ?: return@mapNotNull null
|
||||
} ?: listOf()
|
||||
}
|
||||
|
||||
// Set up TV clock visibility
|
||||
if (isLayout(TV)) {
|
||||
val showTvClock = settingsManager.getBoolean(ctx.getString(R.string.tv_layout_clock_key), false)
|
||||
playerBinding?.playerVideoClock?.isVisible = showTvClock
|
||||
} else {
|
||||
playerBinding?.playerVideoClock?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
unwrapBundle(savedInstanceState)
|
||||
|
|
@ -2357,23 +2297,19 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
observe(viewModel.currentLinks) { (_, instance) ->
|
||||
observe(viewModel.currentLinks) { (links, instance) ->
|
||||
if (instance != viewModel.state.instance) return@observe // Outdated observe
|
||||
|
||||
val sortedLinks = viewModel.state.sortLinks(currentQualityProfile)
|
||||
val usableLinks = sortedLinks.count { link -> link.shouldUseLink }
|
||||
|
||||
val turnVisible = usableLinks > 0 && viewModel.generator?.canSkipLoading == true
|
||||
val turnVisible = links.isNotEmpty() && viewModel.generator?.canSkipLoading == true
|
||||
val wasGone = binding.overlayLoadingSkipButton.isGone
|
||||
|
||||
binding.overlayLoadingSkipButton.apply {
|
||||
isVisible = turnVisible
|
||||
|
||||
if (usableLinks == 0) {
|
||||
if (links.isEmpty()) {
|
||||
setText(R.string.skip_loading)
|
||||
} else {
|
||||
@SuppressLint("SetTextI18n")
|
||||
text = "${context.getString(R.string.skip_loading)} (${usableLinks})"
|
||||
text = "${context.getString(R.string.skip_loading)} (${links.size})"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
|||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority
|
||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
|
|
@ -42,20 +40,12 @@ data class GeneratorState(
|
|||
val id: Int?,
|
||||
)
|
||||
|
||||
data class DisplayLink(
|
||||
val link: VideoLink,
|
||||
// If the link should be displayed and used by the player
|
||||
val shouldUseLink: Boolean,
|
||||
val priority: Int
|
||||
)
|
||||
|
||||
/** Immutable state of all current links relevant to displaying the video */
|
||||
// @MustUseReturnValues
|
||||
// @Immutable
|
||||
data class VideoState(
|
||||
val subtitles: PersistentSet<SubtitleData> = persistentSetOf(),
|
||||
val links: PersistentSet<VideoLink> = persistentSetOf(),
|
||||
val erroredLinks: PersistentSet<VideoLink> = persistentSetOf(),
|
||||
val stamps: PersistentList<VideoSkipStamp> = persistentListOf(),
|
||||
val loading: Resource<Unit> = Resource.Loading(),
|
||||
val generatorState: GeneratorState? = null,
|
||||
|
|
@ -66,52 +56,19 @@ data class VideoState(
|
|||
*
|
||||
* sortedBy is not exactly expensive, but each hasNextMirror does it again, so this alleviates unnecessary recomputation
|
||||
* */
|
||||
private val sortedLinks: ConcurrentHashMap<Int, List<DisplayLink>> = ConcurrentHashMap()
|
||||
private val sortedLinks: ConcurrentHashMap<Int, List<VideoLink>> = ConcurrentHashMap()
|
||||
|
||||
/**
|
||||
* The cache is guaranteed to be up to date link-wise due to the immutable links.
|
||||
* However, hideNegativeSources and hideErrorSources could be updated, which requires clearing the cache.
|
||||
*/
|
||||
fun clearSortedLinksCache() = sortedLinks.clear()
|
||||
|
||||
private fun hasLinkErrored(link: VideoLink): Boolean {
|
||||
return erroredLinks.any { it == link }
|
||||
}
|
||||
|
||||
private fun VideoLink.toDisplayLink(
|
||||
qualityProfile: Int,
|
||||
hideNegativeSources: Boolean,
|
||||
hideErrorSources: Boolean
|
||||
): DisplayLink {
|
||||
val priority = getLinkPriority(qualityProfile, this.first)
|
||||
val shouldHideLink =
|
||||
(hideNegativeSources && priority < 0) || (hideErrorSources && hasLinkErrored(this))
|
||||
val displayLink = DisplayLink(this, !shouldHideLink, priority)
|
||||
|
||||
return displayLink
|
||||
}
|
||||
|
||||
// Modifying sortedLinks is not considered a "visible" side effect, and rerunning it does not change the result
|
||||
// It is by all standards, idempotent and by extension also pure as it has no "visible" side effect
|
||||
/** Returns .links in the sorted order according to the qualityProfile.
|
||||
* Use .links if order is not needed */
|
||||
@Contract(pure = true)
|
||||
fun sortLinks(qualityProfile: Int): List<DisplayLink> {
|
||||
sortedLinks[qualityProfile]?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val hideNegativeSources =
|
||||
QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideNegativeSources)
|
||||
val hideErrorSources =
|
||||
QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideErrorSources)
|
||||
|
||||
return links.map { link ->
|
||||
fun sortLinks(qualityProfile: Int): List<VideoLink> {
|
||||
return sortedLinks[qualityProfile] ?: links.sortedBy { link ->
|
||||
// negative because we want to sort highest quality first
|
||||
link.toDisplayLink(qualityProfile, hideNegativeSources, hideErrorSources)
|
||||
}.sortedBy {
|
||||
// negative because we want to sort highest quality first
|
||||
-it.priority
|
||||
-getLinkPriority(qualityProfile, link.first)
|
||||
}.also { value -> sortedLinks[qualityProfile] = value }
|
||||
}
|
||||
|
||||
|
|
@ -156,12 +113,6 @@ data class VideoState(
|
|||
@JvmName("setVideoSkipStamp")
|
||||
@Contract(pure = true)
|
||||
fun set(items: Collection<VideoSkipStamp>): VideoState = copy(stamps = items.toPersistentList())
|
||||
|
||||
@Contract(pure = true)
|
||||
fun addError(item: VideoLink): VideoState = copy(erroredLinks = erroredLinks.add(item))
|
||||
|
||||
@Contract(pure = true)
|
||||
fun setError(items: Collection<VideoLink>): VideoState = copy(erroredLinks = items.toPersistentSet())
|
||||
}
|
||||
|
||||
data class VideoLive<T>(
|
||||
|
|
@ -190,8 +141,9 @@ class PlayerGeneratorViewModel : ViewModel() {
|
|||
var state = VideoState(instance = 0)
|
||||
private set
|
||||
|
||||
private val _currentLinks = MutableLiveData<VideoLive<Set<VideoLink>>>(null)
|
||||
val currentLinks: LiveData<VideoLive<Set<VideoLink>>> = _currentLinks
|
||||
private val _currentLinks =
|
||||
MutableLiveData<VideoLive<Set<Pair<ExtractorLink?, ExtractorUri?>>>>(null)
|
||||
val currentLinks: LiveData<VideoLive<Set<Pair<ExtractorLink?, ExtractorUri?>>>> = _currentLinks
|
||||
|
||||
private val _currentSubtitles = MutableLiveData<VideoLive<Set<SubtitleData>>>(null)
|
||||
val currentSubtitles: LiveData<VideoLive<Set<SubtitleData>>> = _currentSubtitles
|
||||
|
|
|
|||
|
|
@ -12,16 +12,12 @@ import com.lagradost.cloudstream3.utils.txt
|
|||
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import java.util.EnumMap
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.also
|
||||
import kotlin.math.abs
|
||||
|
||||
object QualityDataHelper {
|
||||
private const val VIDEO_SOURCE_PRIORITY = "video_source_priority"
|
||||
private const val VIDEO_PROFILE_NAME = "video_profile_name"
|
||||
private const val VIDEO_QUALITY_PRIORITY = "video_quality_priority"
|
||||
const val VIDEO_PROFILE_SETTINGS = "video_profile_settings"
|
||||
|
||||
// Old key only supporting one type per profile
|
||||
@Deprecated("Changed to support multiple types per profile")
|
||||
|
|
@ -57,21 +53,13 @@ object QualityDataHelper {
|
|||
val types: Set<QualityProfileType>
|
||||
)
|
||||
|
||||
|
||||
// Map profile and name to priority
|
||||
val sourcePriorityCache: ConcurrentHashMap<Int, HashMap<String, Int>> = ConcurrentHashMap()
|
||||
|
||||
fun getSourcePriority(profile: Int, name: String?): Int {
|
||||
if (name == null) return DEFAULT_SOURCE_PRIORITY
|
||||
|
||||
return sourcePriorityCache[profile]?.get(name) ?: (getKey<Int>(
|
||||
return getKey<Int>(
|
||||
"$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile",
|
||||
name,
|
||||
DEFAULT_SOURCE_PRIORITY
|
||||
) ?: DEFAULT_SOURCE_PRIORITY).also {
|
||||
sourcePriorityCache.getOrPut(profile) { hashMapOf() }
|
||||
sourcePriorityCache[profile]?.set(name, it)
|
||||
}
|
||||
) ?: DEFAULT_SOURCE_PRIORITY
|
||||
}
|
||||
|
||||
fun getAllSourcePriorityNames(profile: Int): List<String> {
|
||||
|
|
@ -89,8 +77,6 @@ object QualityDataHelper {
|
|||
} else {
|
||||
setKey(folder, name, priority)
|
||||
}
|
||||
|
||||
sourcePriorityCache[profile]?.set(name, priority)
|
||||
}
|
||||
|
||||
fun setProfileName(profile: Int, name: String?) {
|
||||
|
|
@ -107,17 +93,12 @@ object QualityDataHelper {
|
|||
?: txt(R.string.profile_number, profile)
|
||||
}
|
||||
|
||||
// Map profile and quality to priority
|
||||
val qualityPriorityCache: ConcurrentHashMap<Int, EnumMap<Qualities, Int>> = ConcurrentHashMap()
|
||||
fun getQualityPriority(profile: Int, quality: Qualities): Int {
|
||||
return qualityPriorityCache[profile]?.get(quality) ?: (getKey<Int>(
|
||||
return getKey<Int>(
|
||||
"$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile",
|
||||
quality.value.toString(),
|
||||
quality.defaultPriority
|
||||
)?.also {
|
||||
qualityPriorityCache.getOrPut(profile) { EnumMap(Qualities::class.java) }
|
||||
qualityPriorityCache[profile]?.set(quality, it)
|
||||
}) ?: quality.defaultPriority
|
||||
) ?: quality.defaultPriority
|
||||
}
|
||||
|
||||
fun setQualityPriority(profile: Int, quality: Qualities, priority: Int) {
|
||||
|
|
@ -126,24 +107,8 @@ object QualityDataHelper {
|
|||
quality.value.toString(),
|
||||
priority
|
||||
)
|
||||
qualityPriorityCache[profile]?.set(quality, priority)
|
||||
}
|
||||
|
||||
fun <T> setProfileSetting(profile: Int, setting: ProfileSettings<T>, value: T) {
|
||||
val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile"
|
||||
// Prevent unnecessary keys
|
||||
if (value == setting.defaultValue) {
|
||||
removeKey(folder, setting.key)
|
||||
} else {
|
||||
setKey(folder, setting.key, value)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> getProfileSetting(profile: Int, setting: ProfileSettings<T>): T {
|
||||
val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile"
|
||||
val value = getKey<T>(folder, setting.key)
|
||||
return value ?: setting.defaultValue
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
fun getQualityProfileTypes(profile: Int): Set<QualityProfileType> {
|
||||
|
|
@ -259,8 +224,3 @@ object QualityDataHelper {
|
|||
return Qualities.entries.minBy { abs(it.value - target) }
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ProfileSettings<T>(val key: String, val defaultValue: T) {
|
||||
object HideErrorSources : ProfileSettings<Boolean>("hide_error_sources", false)
|
||||
object HideNegativeSources : ProfileSettings<Boolean>("hide_negative_sources", false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
|
|||
|
||||
class SourcePriorityDialog(
|
||||
val ctx: Context,
|
||||
@StyleRes val themeRes: Int,
|
||||
@StyleRes themeRes: Int,
|
||||
val links: List<LinkSource>,
|
||||
private val profile: QualityDataHelper.QualityProfile,
|
||||
/**
|
||||
|
|
@ -28,27 +28,28 @@ class SourcePriorityDialog(
|
|||
PlayerSelectSourcePriorityBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||
setContentView(binding.root)
|
||||
fixSystemBarsPadding(binding.root)
|
||||
val sourcesRecyclerView = binding.sortSources
|
||||
val qualitiesRecyclerView = binding.sortQualities
|
||||
val profileText = binding.profileTextEditable
|
||||
val saveBtt = binding.saveBtt
|
||||
val exitBtt = binding.closeBtt
|
||||
val helpBtt = binding.helpBtt
|
||||
|
||||
binding.apply {
|
||||
profileTextEditable.setText(
|
||||
QualityDataHelper.getProfileName(profile.id).asString(context)
|
||||
)
|
||||
profileTextEditable.hint = txt(R.string.profile_number, profile.id).asString(context)
|
||||
profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context))
|
||||
profileText.hint = txt(R.string.profile_number, profile.id).asString(context)
|
||||
|
||||
sortSources.adapter = PriorityAdapter<Nothing?>(
|
||||
sourcesRecyclerView.adapter = PriorityAdapter<Nothing?>(
|
||||
).apply {
|
||||
val sortedLinks = links.map { link ->
|
||||
submitList(links.map { link ->
|
||||
SourcePriority(
|
||||
null,
|
||||
link.source,
|
||||
QualityDataHelper.getSourcePriority(profile.id, link.source)
|
||||
)
|
||||
}.distinctBy { it.name }.sortedBy { -it.priority }
|
||||
|
||||
submitList(sortedLinks)
|
||||
}.distinctBy { it.name }.sortedBy { -it.priority })
|
||||
}
|
||||
|
||||
sortQualities.adapter = PriorityAdapter<Qualities>(
|
||||
qualitiesRecyclerView.adapter = PriorityAdapter<Qualities>(
|
||||
).apply {
|
||||
submitList(Qualities.entries.mapNotNull {
|
||||
SourcePriority(
|
||||
|
|
@ -61,8 +62,8 @@ class SourcePriorityDialog(
|
|||
|
||||
@Suppress("UNCHECKED_CAST") // We know the types
|
||||
saveBtt.setOnClickListener {
|
||||
val qualityAdapter = sortQualities.adapter as? PriorityAdapter<Qualities>
|
||||
val sourcesAdapter = sortSources.adapter as? PriorityAdapter<Nothing?>
|
||||
val qualityAdapter = qualitiesRecyclerView.adapter as? PriorityAdapter<Qualities>
|
||||
val sourcesAdapter = sourcesRecyclerView.adapter as? PriorityAdapter<Nothing?>
|
||||
|
||||
val qualities = qualityAdapter?.immutableCurrentList ?: emptyList()
|
||||
val sources = sourcesAdapter?.immutableCurrentList ?: emptyList()
|
||||
|
|
@ -78,7 +79,7 @@ class SourcePriorityDialog(
|
|||
qualityAdapter?.submitList(qualities.sortedBy { -it.priority })
|
||||
sourcesAdapter?.submitList(sources.sortedBy { -it.priority })
|
||||
|
||||
val savedProfileName = profileTextEditable.text.toString()
|
||||
val savedProfileName = profileText.text.toString()
|
||||
if (savedProfileName.isBlank()) {
|
||||
QualityDataHelper.setProfileName(profile.id, null)
|
||||
} else {
|
||||
|
|
@ -87,8 +88,8 @@ class SourcePriorityDialog(
|
|||
updatedCallback.invoke()
|
||||
}
|
||||
|
||||
closeBtt.setOnClickListener {
|
||||
dismissSafe()
|
||||
exitBtt.setOnClickListener {
|
||||
this.dismissSafe()
|
||||
}
|
||||
|
||||
helpBtt.setOnClickListener {
|
||||
|
|
@ -97,10 +98,6 @@ class SourcePriorityDialog(
|
|||
}.show()
|
||||
}
|
||||
|
||||
settingsBtt.setOnClickListener {
|
||||
SourceProfileSettingsDialog(ctx, themeRes, profile.id).show()
|
||||
}
|
||||
}
|
||||
super.show()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui.player.source_priority
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import androidx.annotation.StyleRes
|
||||
import com.lagradost.cloudstream3.databinding.SourceProfileSettingsDialogBinding
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
|
||||
|
||||
class SourceProfileSettingsDialog(
|
||||
val ctx: Context,
|
||||
@StyleRes themeRes: Int,
|
||||
val profile: Int
|
||||
) : Dialog(ctx, themeRes) {
|
||||
override fun show() {
|
||||
val binding =
|
||||
SourceProfileSettingsDialogBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||
setContentView(binding.root)
|
||||
fixSystemBarsPadding(binding.root)
|
||||
|
||||
binding.apply {
|
||||
var hideErrorSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideErrorSources)
|
||||
var hideNegativeSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideNegativeSources)
|
||||
|
||||
profileHideErrorSources.isChecked = hideErrorSources
|
||||
profileHideErrorSources.setOnCheckedChangeListener { _, bool ->
|
||||
hideErrorSources = bool
|
||||
}
|
||||
|
||||
profileHideNegativeSources.isChecked = hideNegativeSources
|
||||
profileHideNegativeSources.setOnCheckedChangeListener { _, bool ->
|
||||
hideNegativeSources = bool
|
||||
}
|
||||
|
||||
applyBtt.setOnClickListener {
|
||||
QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideErrorSources, hideErrorSources)
|
||||
QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideNegativeSources, hideNegativeSources)
|
||||
dismissSafe()
|
||||
}
|
||||
|
||||
cancelBtt.setOnClickListener {
|
||||
dismissSafe()
|
||||
}
|
||||
}
|
||||
super.show()
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +77,6 @@ val appLanguages = arrayListOf(
|
|||
Pair("Azərbaycan dili", "az"),
|
||||
Pair("Bahasa Indonesia", "in"),
|
||||
Pair("Bahasa Melayu", "ms"),
|
||||
Pair("català", "ca"),
|
||||
Pair("Deutsch", "de"),
|
||||
Pair("English", "en"),
|
||||
Pair("Español", "es"),
|
||||
|
|
@ -98,7 +97,6 @@ val appLanguages = arrayListOf(
|
|||
Pair("Português", "pt"),
|
||||
Pair("Português (Brasil)", "pt-BR"),
|
||||
Pair("Română", "ro"),
|
||||
Pair("Shqip мова", "sq"),
|
||||
Pair("Slovenčina", "sk"),
|
||||
Pair("Soomaaliga", "so"),
|
||||
Pair("Svenska", "sv"),
|
||||
|
|
@ -108,7 +106,6 @@ val appLanguages = arrayListOf(
|
|||
Pair("Wikang Filipino", "fil"),
|
||||
Pair("Čeština", "cs"),
|
||||
Pair("Ελληνικά", "el"),
|
||||
Pair("беларуская мова", "be"),
|
||||
Pair("български", "bg"),
|
||||
Pair("македонски", "mk"),
|
||||
Pair("русский", "ru"),
|
||||
|
|
|
|||
|
|
@ -228,8 +228,6 @@ class SettingsUI : BasePreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
getPref(R.string.tv_layout_clock_key)?.hideOn(PHONE or EMULATOR)
|
||||
|
||||
getPref(R.string.confirm_exit_key)?.setOnPreferenceClickListener {
|
||||
val prefNames = resources.getStringArray(R.array.confirm_exit)
|
||||
val prefValues = resources.getIntArray(R.array.confirm_exit_values)
|
||||
|
|
|
|||
|
|
@ -181,11 +181,11 @@ object DataStore {
|
|||
}
|
||||
|
||||
fun <T : Any> Context.getKey(path: String, valueType: Class<T>): T? {
|
||||
return try {
|
||||
try {
|
||||
val json: String = getSharedPrefs().getString(path, null) ?: return null
|
||||
parseJson(json, valueType.kotlin)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
return parseJson(json, valueType.kotlin)
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,37 +193,21 @@ object DataStore {
|
|||
setKey(getFolderName(folder, path), value)
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Use parseJson<T>(this) directly instead.",
|
||||
level = DeprecationLevel.WARNING,
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "parseJson<T>(this)",
|
||||
imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"],
|
||||
),
|
||||
)
|
||||
inline fun <reified T : Any> String.toKotlinObject(): T {
|
||||
return parseJson(this)
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Use parseJson<T>(this) directly instead.",
|
||||
level = DeprecationLevel.WARNING,
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "parseJson<T>(this)",
|
||||
imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"],
|
||||
),
|
||||
)
|
||||
fun <T : Any> String.toKotlinObject(valueType: Class<T>): T {
|
||||
return parseJson(this, valueType.kotlin)
|
||||
}
|
||||
|
||||
// GET KEY GIVEN PATH AND DEFAULT VALUE, NULL IF ERROR
|
||||
inline fun <reified T : Any> Context.getKey(path: String, defVal: T?): T? {
|
||||
return try {
|
||||
try {
|
||||
val json: String = getSharedPrefs().getString(path, null) ?: return defVal
|
||||
parseJson<T>(json)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
return json.toKotlinObject()
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -149,12 +149,10 @@ object TvChannelUtils {
|
|||
.setInputId(inputId)
|
||||
.build()
|
||||
|
||||
val channelUri = runCatching {
|
||||
context.contentResolver.insert(
|
||||
val channelUri = context.contentResolver.insert(
|
||||
TvContractCompat.Channels.CONTENT_URI,
|
||||
channel.toContentValues()
|
||||
)
|
||||
}.getOrNull()
|
||||
|
||||
channelUri?.let {
|
||||
val channelId = ContentUris.parseId(it)
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="m612,668 l56,-56 -148,-148v-184h-80v216l172,172ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,480ZM480,800q133,0 226.5,-93.5T800,480q0,-133 -93.5,-226.5T480,160q-133,0 -226.5,93.5T160,480q0,133 93.5,226.5T480,800Z"
|
||||
android:fillColor="#e3e3e3"/>
|
||||
</vector>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -18,8 +19,8 @@
|
|||
android:id="@+id/subtitles_click_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
|
||||
|
|
@ -32,21 +33,14 @@
|
|||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/settings_btt"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:text="@string/title_settings" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/help_btt"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/help"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/baseline_help_outline_24" />
|
||||
android:src="@drawable/baseline_help_outline_24"
|
||||
android:contentDescription="@string/help" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
|
@ -99,8 +93,8 @@
|
|||
android:id="@+id/apply_btt_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="?android:attr/listPreferredItemPaddingStart">
|
||||
|
||||
<EditText
|
||||
|
|
@ -114,19 +108,19 @@
|
|||
android:textColor="?attr/textColor"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="LabelFor"
|
||||
tools:text="@string/profile_number" />
|
||||
tools:text="@string/profile_number"
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/save_btt"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/sort_save" />
|
||||
android:text="@string/sort_save"
|
||||
style="@style/WhiteButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/close_btt"
|
||||
style="@style/BlackButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/sort_close" />
|
||||
android:text="@string/sort_close"
|
||||
style="@style/BlackButton" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -227,12 +227,6 @@
|
|||
tools:listitem="@layout/homepage_parent"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<TextClock
|
||||
android:id="@+id/home_clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/home_api_fab"
|
||||
style="@style/ExtendedFloatingActionButton"
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@
|
|||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
|
@ -164,22 +163,6 @@
|
|||
tools:listitem="@layout/homepage_parent_tv"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<TextClock
|
||||
android:id="@+id/home_clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxWidth="600dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:visibility="visible"
|
||||
android:format12Hour="hh:mm a"
|
||||
android:format24Hour="HH:mm"
|
||||
android:fontFamily="@font/google_sans"
|
||||
android:textStyle="bold"
|
||||
android:gravity="start"
|
||||
android:layout_gravity="start"
|
||||
android:layout_margin="10dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_api_holder"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -264,15 +264,6 @@
|
|||
tools:visibility="visible"
|
||||
android:layout_gravity="center"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextClock
|
||||
android:id="@+id/player_video_clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:gravity="end"
|
||||
android:visibility="gone"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Removed as it has no use anymore-->
|
||||
|
|
|
|||
|
|
@ -352,22 +352,6 @@
|
|||
android:layout_marginEnd="32dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextClock
|
||||
android:id="@+id/player_video_clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:gravity="end"
|
||||
android:maxWidth="600dp"
|
||||
android:textAlignment="viewEnd"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
android:visibility="visible"
|
||||
android:format12Hour="hh:mm a"
|
||||
android:format24Hour="HH:mm"
|
||||
android:fontFamily="@font/google_sans"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/player_video_title_holder"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -160,13 +160,6 @@
|
|||
<!-- android:layout_gravity="center"-->
|
||||
<!-- android:src="@drawable/outline_edit_24" />-->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/settings_btt"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:text="@string/title_settings" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/profile_settings_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- <ScrollView-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="match_parent">-->
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:text="@string/profile_settings"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/profile_hide_negative_sources"
|
||||
style="@style/SettingsItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/google_sans"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
android:nextFocusRight="@id/cancel_btt"
|
||||
android:nextFocusDown="@id/profile_hide_error_sources"
|
||||
android:text="@string/profile_hide_negative_sources"
|
||||
app:drawableEndCompat="@null"
|
||||
app:trackTint="@color/toggle_selector" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/profile_hide_error_sources"
|
||||
style="@style/SettingsItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/google_sans"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
android:nextFocusRight="@id/cancel_btt"
|
||||
android:nextFocusUp="@id/profile_hide_negative_sources"
|
||||
android:nextFocusDown="@id/apply_btt"
|
||||
android:text="@string/profile_hide_error_sources"
|
||||
app:drawableEndCompat="@null"
|
||||
app:trackTint="@color/toggle_selector" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="bottom|end"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/apply_btt"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:nextFocusRight="@id/cancel_btt"
|
||||
android:nextFocusUp="@id/subtitles_italic"
|
||||
android:text="@string/sort_apply"
|
||||
android:visibility="visible">
|
||||
|
||||
<requestFocus />
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/cancel_btt"
|
||||
style="@style/BlackButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
android:nextFocusUp="@id/subtitles_remove_captions"
|
||||
android:text="@string/sort_cancel" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
<!-- </ScrollView>-->
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -171,12 +171,6 @@
|
|||
android:layout_marginEnd="32dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextClock
|
||||
android:id="@+id/player_video_clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/player_video_title_holder"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<string name="result_poster_img_des">পোস্টার</string>
|
||||
<string name="play_with_app_name">ক্লাউডস্ট্রিম দিয়ে চালান</string>
|
||||
<string name="title_home">হোম</string>
|
||||
<string name="cast_format" formatted="true">অভিনয়ে: %s</string>
|
||||
<string name="cast_format" formatted="true">অভিনয়েঃ %s</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%1$dদিন %2$dঘন্টা %3$dমিনিট</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%1$dঘন্টা %2$dমিনিট</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%d মিনিট</string>
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<string name="title_search">খুঁজুন</string>
|
||||
<string name="title_downloads">ডাউনলোডসমূহ</string>
|
||||
<string name="title_settings">সেটিংস</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s পর্ব %2$d</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s এপি %2$d</string>
|
||||
<string name="next_episode_format" formatted="true">পর্ব %d মুক্তির তারিখ</string>
|
||||
<string name="result_share">শেয়ার</string>
|
||||
<string name="result_open_in_browser">ব্রাউজারে খুলুন</string>
|
||||
|
|
@ -131,8 +131,8 @@
|
|||
<string name="backup_failed">স্টোরেজ এর অনুমতি অনুপস্থিত। দয়া করে আবার চেষ্টা করুন।</string>
|
||||
<string name="settings_info">তথ্য</string>
|
||||
<string name="show_trailers_settings">ট্রেইলার প্রদর্শন করুন</string>
|
||||
<string name="kitsu_settings">Kitsu থেকে পোস্টারসমূহ প্রদর্শন করুন</string>
|
||||
<string name="automatic_plugin_updates">স্বয়ংক্রিয় প্লাগইন আপডেট</string>
|
||||
<string name="kitsu_settings">কিটসু হতে পোস্টারসমূহ প্রদর্শন করুন</string>
|
||||
<string name="automatic_plugin_updates">স্বয়ংক্রিয়ভাবে প্লাগিন এর হালনাগাদ</string>
|
||||
<string name="automatic_plugin_download">স্বয়ংক্রিয়ভাবে প্লাগিনসমুহের ডাউনলোড</string>
|
||||
<string name="category_updates">হালনাগাদ ও ব্যাকআপ</string>
|
||||
<string name="updates_settings">অ্যাপ এর হালনাগাদ দেখান</string>
|
||||
|
|
@ -148,9 +148,9 @@
|
|||
<string name="double_tap_to_seek_settings_des">সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন</string>
|
||||
<string name="delete_file">ফাইল ডিলিট</string>
|
||||
<string name="subs_default_reset_toast">মান ডিফল্ট এ রিসেট করুন</string>
|
||||
<string name="updates_settings_des">স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন যোগ।</string>
|
||||
<string name="updates_settings_des">স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন</string>
|
||||
<string name="movies_singular">সিনেমা</string>
|
||||
<string name="show_fillers_settings">অ্যানিমে ফিলার পর্ব প্রদর্শন</string>
|
||||
<string name="show_fillers_settings">এনিমে এর ফিলার পর্ব দেখায়</string>
|
||||
<string name="tv_series">টিভি সিরিজ</string>
|
||||
<string name="no_links_found_toast">লিংক পাওয়া যায়নি</string>
|
||||
<string name="benene_des">চা খাওয়ানো হয়েছে</string>
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
<string name="delete">ডিলিট</string>
|
||||
<string name="start">শুরু</string>
|
||||
<string name="cartoons">কার্টুন</string>
|
||||
<string name="apk_installer_settings_des">Some devices do not support the new package installer. Try the legacy option if updates do not install।</string>
|
||||
<string name="apk_installer_settings_des">কিছু ফোন নতুন প্যাকেজ ইনস্টলার সাপোর্ট করে না। যদি আপডেটগুলি ইনস্টল না হয় তবে পুরোনো পদ্ধতি ব্যবহার করে দেখুন।</string>
|
||||
<string name="no_subtitles">সাবটাইটেল নেই</string>
|
||||
<string name="no_chromecast_support_toast">এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না</string>
|
||||
<string name="advanced_search">উন্নত অনুসন্ধান</string>
|
||||
|
|
@ -204,7 +204,7 @@
|
|||
<string name="anim">আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন</string>
|
||||
<string name="nsfw">18+</string>
|
||||
<string name="anime">এনিমে</string>
|
||||
<string name="pref_filter_search_quality">অনুসন্ধানের ফলে নির্বাচিত ভিডিওর মান লুকান</string>
|
||||
<string name="pref_filter_search_quality">অনুসন্ধান ফলাফলে নির্বাচিত ভিডিও কুয়ালিটি লুকান</string>
|
||||
<string name="app_storage">অ্যাপ</string>
|
||||
<string name="livestreams">লাইভস্ট্রিম</string>
|
||||
<string name="apk_installer_settings">APK ইনস্টলার</string>
|
||||
|
|
@ -354,37 +354,4 @@
|
|||
<string name="speech_recognition_unavailable">স্পিচ রিকগনিশন উপলব্ধ নেই</string>
|
||||
<string name="begin_speaking">কথা বলা শুরু করুন…</string>
|
||||
<string name="download_queue">ডাউনলোড তালিকা</string>
|
||||
<string name="play_full_series_button">সম্পূর্ণ সিরিজ প্লে করুন</string>
|
||||
<string name="torrent_info">এই ভিডিওটি একটি টরেন্ট, অর্থাৎ আপনার ভিডিও অ্যাক্টিভিটি ট্র্যাক করা সম্ভব।\nএগোনোর আগে টরেন্টিং সম্পর্কে ভালোভাবে বুঝে নিন।</string>
|
||||
<string name="downloads_delete_select">ডিলিট করার জন্য আইটেম সিলেক্ট করুন</string>
|
||||
<string name="downloads_empty">বর্তমানে কোনো ডাউনলোড নেই।</string>
|
||||
<string name="queue_empty_message">বর্তমানে কোনো ডাউনলোড কিউতে নেই।</string>
|
||||
<string name="offline_file">অফলাইনে দেখার জন্য উপলব্ধ</string>
|
||||
<string name="select_all">সব সিলেক্ট করুন</string>
|
||||
<string name="deselect_all">সব ডিসিলেক্ট করুন</string>
|
||||
<string name="open_local_video">লোকাল ভিডিও খুলুন</string>
|
||||
<string name="extra_brightness_settings">অতিরিক্ত ব্রাইটনেস</string>
|
||||
<string name="extra_brightness_settings_des">ডিসপ্লে ব্রাইটনেস ১০০% ছাড়িয়ে গেলে ব্রাইটনেস ফিল্টার চালু করুন</string>
|
||||
<string name="search_suggestions">সার্চ সাজেশন</string>
|
||||
<string name="search_suggestions_des">টাইপ করার সময় সার্চ সাজেশন দেখান</string>
|
||||
<string name="clear_suggestions">সাজেশন মুছে ফেলুন</string>
|
||||
<string name="show_player_metadata_overlay">প্লেয়ার মেটাডেটা ওভারলে প্রদর্শন</string>
|
||||
<string name="show_cast_in_details">কাস্ট প্যানেল প্রদর্শন</string>
|
||||
<string name="install_prerelease">প্রাক-মুক্তি সংস্করণ ইনস্টলেশন</string>
|
||||
<string name="prerelease_already_installed">প্রাক-মুক্তি সংস্করণ ইতোমধ্যে ইনস্টল রয়েছে।</string>
|
||||
<string name="prerelease_install_failed">প্রাক-মুক্তি সংস্করণ ইনস্টল করতে সমস্যা হয়েছে।</string>
|
||||
<string name="delete_format" formatted="true">(%1$d | %2$s) মুছুন</string>
|
||||
<string name="test_warning">সাবধান</string>
|
||||
<string name="delete_message_multiple" formatted="true">আপনি কি নিম্নলিখিত আইটেমগুলো স্থায়ীভাবে মুছতে চান?\n\n%s</string>
|
||||
<string name="delete_files">ফাইল মুছুন</string>
|
||||
<string name="delete_message_series_episodes" formatted="true">আপনি কি নিশ্চিত যে আপনি %1$s-এ নিচের এপিসোডগুলো স্থায়ীভাবে মুছে ফেলতে চান?\n\n%2$s</string>
|
||||
<string name="delete_message_series_section" formatted="true">আপনি নিচের সিরিজগুলোর সব এপিসোডও স্থায়ীভাবে মুছে ফেলবেন:\n\n%s</string>
|
||||
<string name="delete_message_series_only" formatted="true">আপনি কি নিশ্চিত যে আপনি নিচের সিরিজটির সব এপিসোড স্থায়ীভাবে মুছে ফেলতে চান?\n\n%s</string>
|
||||
<string name="music_singular">সঙ্গীত</string>
|
||||
<string name="audio_book_singular">অডিও বই</string>
|
||||
<string name="custom_media_singular">মিডিয়া</string>
|
||||
<string name="audio_singular">অডিও</string>
|
||||
<string name="podcast_singular">পডকাস্ট</string>
|
||||
<string name="video_singular">ভিডিও</string>
|
||||
<string name="encoding_error">এনকোডিং ত্রুটি</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -682,17 +682,4 @@
|
|||
<string name="torrent_preferred_media">Zet torrent aan in Instellingen/Providers/Media voorkeur</string>
|
||||
<string name="torrent_not_accepted">Herstart de app en accepteer de Stream Torrent pop-up om verder te gaan.</string>
|
||||
<string name="update_plugins_manually">Handmatige Update Knop</string>
|
||||
<string name="biometric_setting_summary">Open de app met Vingerafdruk, Face ID, PIN, Pattern en Wachtwoord.</string>
|
||||
<string name="preview_seekbar">Seekbar preview</string>
|
||||
<string name="preview_seekbar_desc">Zet preview thumbnail aan op seekbar</string>
|
||||
<string name="software_decoding">Software decodering</string>
|
||||
<string name="software_decoding_desc">Software decodering staat de mogelijkheid toe om de speler videobestanden af te laten spelen op jouw apparaat, maar het kan een haperige of onstabiele kijkervaring zorgen op hoge resolutie.</string>
|
||||
<string name="volume_exceeded_100">Volume heeft de 100% overschreden</string>
|
||||
<string name="update_plugins">Update Plugins</string>
|
||||
<string name="no_plugins_updated_manually">Geen plugins zijn geupdate.</string>
|
||||
<string name="player_notification_channel_name">Speler meldingen</string>
|
||||
<string name="player_notification_channel_description">De speler melding om de kijkervaring van de achtergrond de besturen</string>
|
||||
<string name="subtitles_from_online">Online</string>
|
||||
<string name="download_parallel_settings_des">Hoeveel verschillende items kunnen in parallel gedownload worden</string>
|
||||
<string name="parallel_downloads">Parallel downloads</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -742,13 +742,4 @@
|
|||
<string name="video_singular">Wideo</string>
|
||||
<string name="skip_type_preview">Zapowiedź</string>
|
||||
<string name="player_is_live">Na żywo</string>
|
||||
<string name="profile_settings">Ustawienia profilu</string>
|
||||
<string name="profile_hide_negative_sources">Ukryj źródła z negatywnym priorytetem</string>
|
||||
<string name="profile_hide_error_sources">Ukryj źródła z błędami</string>
|
||||
<plurals name="links_hidden">
|
||||
<item quantity="one">%d ukryty odnośnik</item>
|
||||
<item quantity="few">%d ukryte odnośniki</item>
|
||||
<item quantity="many">%d ukrytych odnośników</item>
|
||||
<item quantity="other">%d ukrytych odnośników</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<string name="next_episode">Tập tiếp theo</string>
|
||||
<string name="result_tags">Thể loại</string>
|
||||
<string name="result_share">Chia sẻ</string>
|
||||
<string name="result_open_in_browser">Phát bằng Trình duyệt</string>
|
||||
<string name="result_open_in_browser">Mở bằng trình duyệt</string>
|
||||
<string name="skip_loading">Bỏ tải</string>
|
||||
<string name="loading">Đang tải…</string>
|
||||
<string name="type_watching">Đang xem</string>
|
||||
|
|
@ -255,13 +255,13 @@
|
|||
<string name="watch_quality_pref">Chất lượng xem ưu tiên (WiFi)</string>
|
||||
<string name="limit_title">Số ký tự tối đa tiêu đề trình phát</string>
|
||||
<string name="limit_title_rez">Hiển thị thông tin trình phát</string>
|
||||
<string name="video_buffer_size_settings">Kích thước bộ đệm video</string>
|
||||
<string name="video_buffer_length_settings">Thời lượng bộ đệm video</string>
|
||||
<string name="video_buffer_size_settings">Kích thước bộ nhớ đệm video</string>
|
||||
<string name="video_buffer_length_settings">Thời lượng bộ nhớ đệm</string>
|
||||
<string name="video_buffer_disk_settings">Bộ nhớ đệm video trên thiết bị</string>
|
||||
<string name="video_buffer_clear_settings">Xoá bộ nhớ đệm hình ảnh và video</string>
|
||||
<string name="video_ram_description">Sẽ gây lỗi nếu đặt quá cao trên thiết bị có bộ nhớ thấp như Android TV.</string>
|
||||
<string name="video_disk_description">Sẽ gây lỗi nếu đặt quá cao trên thiết bị có dung lượng lưu trữ thấp như Android TV.</string>
|
||||
<string name="dns_pref">DNS qua HTTPS</string>
|
||||
<string name="video_disk_description">Sẽ gây lỗi nếu đặt quá cao trên máy có dung lượng lưu trữ thấp như Android TV.</string>
|
||||
<string name="dns_pref">DNS over HTTPS</string>
|
||||
<string name="dns_pref_summary">Rất hữu ích để bỏ chặn ISP</string>
|
||||
<string name="add_site_pref">Bản sao trang web</string>
|
||||
<string name="remove_site_pref">Xoá trang web</string>
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@
|
|||
<string name="continue_watching">Працягнуць прагляд</string>
|
||||
<string name="action_remove_watching">Выдаліць</string>
|
||||
<string name="action_open_watching">Больш інфармацыі</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN</string>
|
||||
<string name="vpn_torrent">Гэты пастаўшчык — Torrent, рэкамендуецца VPN</string>
|
||||
<string name="provider_info_meta">Вэб-сайт не пастаўляе метададзеных, загрузіць відэа не ўдасца, калі на сайце яго няма.</string>
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
<string name="continue_watching">Continua mirant</string>
|
||||
<string name="action_remove_watching">Eliminar</string>
|
||||
<string name="action_open_watching">Més info</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Potser necessiteu una VPN per que aquest proveïdor funcioni correctament</string>
|
||||
<string name="vpn_torrent">Aquest proveïdor és un Torrent, es recomana fer servir una VPN</string>
|
||||
<string name="provider_info_meta">El lloc no proveeix metadades, la càrrega del video fallarà si no existeix al lloc.</string>
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
<string name="continue_watching">Vazhdo shikimin</string>
|
||||
<string name="action_remove_watching">Hiq</string>
|
||||
<string name="action_open_watching">Më shumë informacion</string>
|
||||
<string name="action_open_play">@string/home_play</string>
|
||||
<string name="action_open_play">\@string/home_play</string>
|
||||
<string name="vpn_might_be_needed">Një VPN mund të nevojitet që ky ofrues të funksionojë në rregull</string>
|
||||
<string name="vpn_torrent">Ky ofrues është Torrent, rekomandohet një VPN</string>
|
||||
<string name="provider_info_meta">Metadata nuk ofrohet nga kjo faqe, ngarkimi i videos do të dështojë nëse nuk ekziston në këtë faqe.</string>
|
||||
|
|
@ -735,11 +735,4 @@
|
|||
<item quantity="other">%d shkarkime në radhë</item>
|
||||
</plurals>
|
||||
<string name="player_is_live">Live</string>
|
||||
<string name="profile_settings">Cilësimet e profil-it</string>
|
||||
<string name="profile_hide_negative_sources">Fshih burimet me prioritet negativ</string>
|
||||
<string name="profile_hide_error_sources">Fshih burimet me error-e</string>
|
||||
<plurals name="links_hidden">
|
||||
<item quantity="one">%d link i fshehur</item>
|
||||
<item quantity="other">%d linqe të fshehura</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
|
@ -101,7 +101,6 @@
|
|||
<string name="opensubtitles_key">opensubtitles_key</string>
|
||||
<string name="subdl_key">subdl_key</string>
|
||||
<string name="animeskip_key">animeskip_key</string>
|
||||
<string name="tv_layout_clock_key">tv_layout_clock_key</string>
|
||||
|
||||
<string name="pref_category_security_key">pref_category_security_key</string>
|
||||
<string name="pref_category_gestures_key">pref_category_gestures_key</string>
|
||||
|
|
|
|||
|
|
@ -374,8 +374,6 @@
|
|||
<string name="pref_category_looks">Looks</string>
|
||||
<string name="pref_category_ui_features">Features</string>
|
||||
<string name="category_general">General</string>
|
||||
<string name="tv_layout_clock_settings">Show real time clock</string>
|
||||
<string name="tv_layout_clock_settings_des">Show a real time clock at the top of the screen. Applies to the homepage and player</string>
|
||||
<string name="random_button_settings">Random Button</string>
|
||||
<string name="random_button_settings_desc">Show random button on Homepage and Library</string>
|
||||
<string name="provider_lang_settings">Extension languages</string>
|
||||
|
|
@ -624,14 +622,6 @@
|
|||
<string name="action_subscribe">Subscribe</string>
|
||||
<string name="action_unsubscribe">Unsubscribe</string>
|
||||
<string name="profile_number">Profile %d</string>
|
||||
<string name="profile_settings">Profile settings</string>
|
||||
<string name="profile_hide_negative_sources">Hide sources with a negative priority</string>
|
||||
<string name="profile_hide_error_sources">Hide sources with errors</string>
|
||||
<plurals name="links_hidden">
|
||||
<item quantity="one">%d hidden link</item>
|
||||
<item quantity="other">%d hidden links</item>
|
||||
</plurals>
|
||||
|
||||
<string name="wifi">Wi-Fi</string>
|
||||
<string name="mobile_data">Mobile data</string>
|
||||
<string name="set_default">Set default</string>
|
||||
|
|
@ -793,4 +783,5 @@
|
|||
<item quantity="other">%d downloads queued</item>
|
||||
</plurals>
|
||||
<string name="player_is_live">Live</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -89,12 +89,6 @@
|
|||
android:icon="@drawable/metadata_overlay_icon"
|
||||
android:key="@string/show_player_metadata_key"
|
||||
android:title="@string/show_player_metadata_overlay" />
|
||||
<SwitchPreference
|
||||
android:icon="@drawable/ic_baseline_clock_24"
|
||||
android:summary="@string/tv_layout_clock_settings_des"
|
||||
android:title="@string/tv_layout_clock_settings"
|
||||
android:defaultValue="false"
|
||||
android:key="@string/tv_layout_clock_key" />
|
||||
<SwitchPreference
|
||||
android:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
android:summary="@string/random_button_settings_desc"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
- চেঞ্জলগ যোগ করা হয়েছে!
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
CloudStream-3 দিয়ে সিনেমা, টিভি-সিরিজ ও অ্যানিমে স্ট্রিম ও ডাউনলোড করা যায়।
|
||||
|
||||
অ্যাপে কোনো বিজ্ঞাপন ও অ্যানালিটিক্স নেই এবং
|
||||
এটি একাধিক ট্রেলার ও মুভি সাইট সাপোর্ট করে, আরও আছে যেমন—
|
||||
বুকমার্ক
|
||||
সাবটাইটেল ডাউনলোড
|
||||
ক্রোমকাস্ট সাপোর্ট
|
||||
|
|
@ -1 +0,0 @@
|
|||
সিনেমা, টিভি সিরিজ ও অ্যানিমে স্ট্রিম ও ডাউনলোড করুন।
|
||||
|
|
@ -1 +0,0 @@
|
|||
CloudStream
|
||||
Loading…
Add table
Add a link
Reference in a new issue