Added quality profiles (#414)

* Added quality profiles

* Better quality selection

* Added profile bg and fixed some sources (#483)

Co-authored-by: Blatzar <>

---------

Co-authored-by: Lag <>
Co-authored-by: Osten <11805592+LagradOst@users.noreply.github.com>
This commit is contained in:
LagradOst 2023-06-14 17:30:39 +00:00 committed by GitHub
parent 0d431fd508
commit b5566af401
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1188 additions and 90 deletions

View file

@ -58,7 +58,7 @@ open class DoodLaExtractor : ExtractorApi() {
val quality = Regex("\\d{3,4}p").find(response0.substringAfter("<title>").substringBefore("</title>"))?.groupValues?.get(0)
return listOf(
ExtractorLink(
trueUrl,
this.name,
this.name,
trueUrl,
mainUrl,

View file

@ -58,7 +58,7 @@ open class GuardareStream : ExtractorApi() {
jsonVideoData.data.forEach {
callback.invoke(
ExtractorLink(
it.file + ".${it.type}",
this.name,
this.name,
it.file + ".${it.type}",
mainUrl,

View file

@ -30,7 +30,7 @@ open class Tantifilm : ExtractorApi() {
val jsonvideodata = parseJson<TantifilmJsonData>(response)
return jsonvideodata.data.map {
ExtractorLink(
it.file+".${it.type}",
this.name,
this.name,
it.file+".${it.type}",
mainUrl,

View file

@ -39,6 +39,7 @@ import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@ -108,8 +109,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
// get() = episodes.isNotEmpty()
// options for player
protected var currentPrefQuality =
Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell
/**
* Default profile 1
* Decides how links should be sorted based on a priority system.
* This will be set in runtime based on settings.
**/
protected var currentQualityProfile = 1
// protected var currentPrefQuality =
// Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell
protected var fastForwardTime = 10000L
protected var androidTVInterfaceOffSeekTime = 10000L;
protected var androidTVInterfaceOnSeekTime = 30000L;
@ -1221,10 +1229,16 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
.toLong() * 1000L
androidTVInterfaceOffSeekTime =
settingsManager.getInt(ctx.getString(R.string.android_tv_interface_off_seek_key), 10)
settingsManager.getInt(
ctx.getString(R.string.android_tv_interface_off_seek_key),
10
)
.toLong() * 1000L
androidTVInterfaceOnSeekTime =
settingsManager.getInt(ctx.getString(R.string.android_tv_interface_on_seek_key), 10)
settingsManager.getInt(
ctx.getString(R.string.android_tv_interface_on_seek_key),
10
)
.toLong() * 1000L
navigationBarHeight = ctx.getNavigationBarHeight()
@ -1257,10 +1271,20 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
ctx.getString(R.string.double_tap_pause_enabled_key),
false
)
currentPrefQuality = settingsManager.getInt(
ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key),
currentPrefQuality
)
val profiles = QualityDataHelper.getProfiles()
val type = if (ctx.isUsingMobileData())
QualityDataHelper.QualityProfileType.Data
else QualityDataHelper.QualityProfileType.WiFi
currentQualityProfile =
profiles.firstOrNull { it.type == type }?.id ?: profiles.firstOrNull()?.id
?: currentQualityProfile
// currentPrefQuality = settingsManager.getInt(
// ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key),
// currentPrefQuality
// )
// useSystemBrightness =
// settingsManager.getBoolean(ctx.getString(R.string.use_system_brightness_key), false)
}

View file

@ -31,6 +31,10 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitl
import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioTrackLanguage
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.QualityDataHelper
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
import com.lagradost.cloudstream3.ui.player.source_priority.SourcePriority
import com.lagradost.cloudstream3.ui.player.source_priority.SourcePriorityDialog
import com.lagradost.cloudstream3.ui.result.*
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
@ -57,6 +61,7 @@ import kotlinx.coroutines.Job
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.abs
class GeneratorPlayer : FullScreenPlayer() {
companion object {
@ -188,17 +193,31 @@ class GeneratorPlayer : FullScreenPlayer() {
player.addTimeStamps(listOf()) // clear stamps
}
private fun sortLinks(useQualitySettings: Boolean = true): List<Pair<ExtractorLink?, ExtractorUri?>> {
return currentLinks.sortedBy {
val (linkData, _) = it
var quality = linkData?.quality ?: Qualities.Unknown.value
private fun closestQuality(target: Int?): Qualities {
if (target == null) return Qualities.Unknown
return Qualities.values().minBy { abs(it.value - target) }
}
// we set all qualities above current max as reverse
if (useQualitySettings && quality > currentPrefQuality) {
quality = currentPrefQuality - quality - 1
}
// negative because we want to sort highest quality first
-(quality)
private fun getLinkPriority(
qualityProfile: Int,
link: Pair<ExtractorLink?, ExtractorUri?>
): Int {
val (linkData, _) = link
val qualityPriority = QualityDataHelper.getQualityPriority(
qualityProfile,
closestQuality(linkData?.quality)
)
val sourcePriority =
QualityDataHelper.getSourcePriority(qualityProfile, linkData?.name)
// negative because we want to sort highest quality first
return qualityPriority + sourcePriority
}
private fun sortLinks(qualityProfile: Int): List<Pair<ExtractorLink?, ExtractorUri?>> {
return currentLinks.sortedBy {
-getLinkPriority(qualityProfile, it)
}
}
@ -584,33 +603,39 @@ class GeneratorPlayer : FullScreenPlayer() {
var sourceIndex = 0
var startSource = 0
var sortedUrls = emptyList<Pair<ExtractorLink?, ExtractorUri?>>()
val sortedUrls = sortLinks(useQualitySettings = false)
if (sortedUrls.isEmpty()) {
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone = true
} else {
startSource = sortedUrls.indexOf(currentSelectedLink)
sourceIndex = startSource
fun refreshLinks(qualityProfile: Int) {
sortedUrls = sortLinks(qualityProfile)
if (sortedUrls.isEmpty()) {
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone =
true
} else {
startSource = sortedUrls.indexOf(currentSelectedLink)
sourceIndex = startSource
val sourcesArrayAdapter =
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
val sourcesArrayAdapter =
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) ->
val name = link?.name ?: uri?.name ?: "NULL"
"$name ${Qualities.getStringByInt(link?.quality)}"
})
sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) ->
val name = link?.name ?: uri?.name ?: "NULL"
"$name ${Qualities.getStringByInt(link?.quality)}"
})
providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
providerList.adapter = sourcesArrayAdapter
providerList.setSelection(sourceIndex)
providerList.setItemChecked(sourceIndex, true)
providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
providerList.adapter = sourcesArrayAdapter
providerList.setSelection(sourceIndex)
providerList.setItemChecked(sourceIndex, true)
providerList.setOnItemClickListener { _, _, which, _ ->
sourceIndex = which
providerList.setItemChecked(which, true)
providerList.setOnItemClickListener { _, _, which, _ ->
sourceIndex = which
providerList.setItemChecked(which, true)
}
}
}
refreshLinks(currentQualityProfile)
sourceDialog.setOnDismissListener {
if (shouldDismiss) dismiss()
selectSourceDialog = null
@ -650,6 +675,29 @@ class GeneratorPlayer : FullScreenPlayer() {
sourceDialog.dismissSafe(activity)
}
fun setProfileName(profile: Int) {
sourceDialog.source_settings_btt.setText(
QualityDataHelper.getProfileName(
profile
)
)
}
setProfileName(currentQualityProfile)
sourceDialog.profiles_click_settings.setOnClickListener {
val activity = activity ?: return@setOnClickListener
QualityProfileDialog(
activity,
R.style.AlertDialogCustomBlack,
currentLinks.mapNotNull { it.first },
currentQualityProfile
) { profile ->
currentQualityProfile = profile.id
setProfileName(profile.id)
refreshLinks(profile.id)
}.show()
}
sourceDialog.subtitles_encoding_format?.apply {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
@ -847,7 +895,7 @@ class GeneratorPlayer : FullScreenPlayer() {
private fun startPlayer() {
if (isActive) return // we don't want double load when you skip loading
val links = sortLinks()
val links = sortLinks(currentQualityProfile)
if (links.isEmpty()) {
noLinksFound()
return
@ -868,12 +916,12 @@ class GeneratorPlayer : FullScreenPlayer() {
}
override fun hasNextMirror(): Boolean {
val links = sortLinks()
val links = sortLinks(currentQualityProfile)
return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size
}
override fun nextMirror() {
val links = sortLinks()
val links = sortLinks(currentQualityProfile)
if (links.isEmpty()) {
noLinksFound()
return
@ -1314,6 +1362,15 @@ class GeneratorPlayer : FullScreenPlayer() {
val turnVisible = it.isNotEmpty()
val wasGone = overlay_loading_skip_button?.isGone == true
overlay_loading_skip_button?.isVisible = turnVisible
normalSafeApiCall {
currentLinks.lastOrNull()?.let { last ->
if (getLinkPriority(currentQualityProfile, last) >= QualityDataHelper.AUTO_SKIP_PRIORITY) {
startPlayer()
}
}
}
if (turnVisible && wasGone) {
overlay_loading_skip_button?.requestFocus()
}

View file

@ -0,0 +1,60 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.AppUtils
import kotlinx.android.synthetic.main.player_prioritize_item.view.*
data class SourcePriority<T>(
val data: T,
val name: String,
var priority: Int
)
class PriorityAdapter<T>(override val items: MutableList<SourcePriority<T>>) :
AppUtils.DiffAdapter<SourcePriority<T>>(items) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return PriorityViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.player_prioritize_item, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is PriorityViewHolder -> holder.bind(items[position])
}
}
class PriorityViewHolder(
itemView: View,
) : RecyclerView.ViewHolder(itemView) {
fun <T> bind(item: SourcePriority<T>) {
val plusButton: ImageView = itemView.add_button
val subtractButton: ImageView = itemView.subtract_button
val priorityText: TextView = itemView.priority_text
val priorityNumber: TextView = itemView.priority_number
priorityText.text = item.name
fun updatePriority() {
priorityNumber.text = item.priority.toString()
}
updatePriority()
plusButton.setOnClickListener {
// If someone clicks til the integer limit then they deserve to crash.
item.priority++
updatePriority()
}
subtractButton.setOnClickListener {
item.priority--
updatePriority()
}
}
}
}

View file

@ -0,0 +1,116 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.result.UiImage
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.player_quality_profile_item.view.card_view
import kotlinx.android.synthetic.main.player_quality_profile_item.view.outline
import kotlinx.android.synthetic.main.player_quality_profile_item.view.profile_image_background
import kotlinx.android.synthetic.main.player_quality_profile_item.view.profile_text
import kotlinx.android.synthetic.main.player_quality_profile_item.view.text_is_mobile_data
import kotlinx.android.synthetic.main.player_quality_profile_item.view.text_is_wifi
class ProfilesAdapter(
override val items: MutableList<QualityDataHelper.QualityProfile>,
val usedProfile: Int,
val clickCallback: (oldIndex: Int?, newIndex: Int) -> Unit,
) :
AppUtils.DiffAdapter<QualityDataHelper.QualityProfile>(
items,
comparison = { first: QualityDataHelper.QualityProfile, second: QualityDataHelper.QualityProfile ->
first.id == second.id
}) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ProfilesViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.player_quality_profile_item, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ProfilesViewHolder -> holder.bind(items[position], position)
}
}
private var currentItem: Pair<Int, QualityDataHelper.QualityProfile>? = null
fun getCurrentProfile(): QualityDataHelper.QualityProfile? {
return currentItem?.second
}
inner class ProfilesViewHolder(
itemView: View,
) : RecyclerView.ViewHolder(itemView) {
private val art = listOf(
R.drawable.profile_bg_teal,
R.drawable.profile_bg_blue,
R.drawable.profile_bg_dark_blue,
R.drawable.profile_bg_purple,
R.drawable.profile_bg_pink,
R.drawable.profile_bg_red,
R.drawable.profile_bg_orange,
)
fun bind(item: QualityDataHelper.QualityProfile, index: Int) {
val priorityText: TextView = itemView.profile_text
val profileBg: ImageView = itemView.profile_image_background
val wifiText: TextView = itemView.text_is_wifi
val dataText: TextView = itemView.text_is_mobile_data
val outline: View = itemView.outline
val cardView: View = itemView.card_view
priorityText.text = item.name.asString(itemView.context)
dataText.isVisible = item.type == QualityDataHelper.QualityProfileType.Data
wifiText.isVisible = item.type == QualityDataHelper.QualityProfileType.WiFi
fun setCurrentItem() {
val prevIndex = currentItem?.first
// Prevent UI bug when re-selecting the item quickly
if (prevIndex == index) {
return
}
currentItem = index to item
clickCallback.invoke(prevIndex, index)
}
outline.isVisible = currentItem?.second?.id == item.id
profileBg.setImage(UiImage.Drawable(art[index % art.size]), null, false) { palette ->
val color = palette.getDarkVibrantColor(
ContextCompat.getColor(
itemView.context,
R.color.dubColorBg
)
)
wifiText.backgroundTintList = ColorStateList.valueOf(color)
dataText.backgroundTintList = ColorStateList.valueOf(color)
}
val textStyle =
if (item.id == usedProfile) {
Typeface.BOLD
} else {
Typeface.NORMAL
}
priorityText.setTypeface(null, textStyle)
cardView.setOnClickListener {
setCurrentItem()
}
}
}
}

View file

@ -0,0 +1,159 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.content.Context
import androidx.annotation.StringRes
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.ui.result.UiText
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
import com.lagradost.cloudstream3.utils.Qualities
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"
private const val VIDEO_PROFILE_TYPE = "video_profile_type"
private const val DEFAULT_SOURCE_PRIORITY = 1
/**
* Automatically skip loading links once this priority is reached
**/
const val AUTO_SKIP_PRIORITY = 10
/**
* Must be higher than amount of QualityProfileTypes
**/
private const val PROFILE_COUNT = 7
/**
* Unique guarantees that there will always be one of this type in the profile list.
**/
enum class QualityProfileType(@StringRes val stringRes: Int, val unique: Boolean) {
None(R.string.none, false),
WiFi(R.string.wifi, true),
Data(R.string.mobile_data, true)
}
data class QualityProfile(
val name: UiText,
val id: Int,
val type: QualityProfileType
)
fun getSourcePriority(profile: Int, name: String?): Int {
if (name == null) return DEFAULT_SOURCE_PRIORITY
return getKey(
"$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile",
name,
DEFAULT_SOURCE_PRIORITY
) ?: DEFAULT_SOURCE_PRIORITY
}
fun setSourcePriority(profile: Int, name: String, priority: Int) {
setKey("$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile", name, priority)
}
fun setProfileName(profile: Int, name: String?) {
val path = "$currentAccount/$VIDEO_PROFILE_NAME/$profile"
if (name == null) {
removeKey(path)
} else {
setKey(path, name.trim())
}
}
fun getProfileName(profile: Int): UiText {
return getKey<String>("$currentAccount/$VIDEO_PROFILE_NAME/$profile")?.let { txt(it) }
?: txt(R.string.profile_number, profile)
}
fun getQualityPriority(profile: Int, quality: Qualities): Int {
return getKey(
"$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile",
quality.value.toString(),
quality.defaultPriority
) ?: quality.defaultPriority
}
fun setQualityPriority(profile: Int, quality: Qualities, priority: Int) {
setKey(
"$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile",
quality.value.toString(),
priority
)
}
fun getQualityProfileType(profile: Int): QualityProfileType {
return getKey("$currentAccount/$VIDEO_PROFILE_TYPE/$profile") ?: QualityProfileType.None
}
fun setQualityProfileType(profile: Int, type: QualityProfileType?) {
val path = "$currentAccount/$VIDEO_PROFILE_TYPE/$profile"
if (type == QualityProfileType.None) {
removeKey(path)
} else {
setKey(path, type)
}
}
/**
* Gets all quality profiles, always includes one profile with WiFi and Data
* Must under all circumstances at least return one profile
**/
fun getProfiles(): List<QualityProfile> {
val availableTypes = QualityProfileType.values().toMutableList()
val profiles = (1..PROFILE_COUNT).map { profileNumber ->
// Get the real type
val type = getQualityProfileType(profileNumber)
// This makes it impossible to get more than one of each type
// Duplicates will be turned to None
val uniqueType = if (type.unique && !availableTypes.remove(type)) {
QualityProfileType.None
} else {
type
}
QualityProfile(
getProfileName(profileNumber),
profileNumber,
uniqueType
)
}.toMutableList()
/**
* If no profile of this type exists: insert it on the earliest profile with None type
**/
fun insertType(
list: MutableList<QualityProfile>,
type: QualityProfileType
) {
if (list.any { it.type == type }) return
val index =
list.indexOfFirst { it.type == QualityProfileType.None }
list.getOrNull(index)?.copy(type = type)
?.let { fixed ->
list.set(index, fixed)
}
}
QualityProfileType.values().forEach {
if (it.unique) insertType(profiles, it)
}
debugAssert({
!QualityProfileType.values().all { type ->
!type.unique || profiles.any { it.type == type }
}
}, { "All unique quality types do not exist" })
debugAssert({
profiles.isEmpty()
}, { "No profiles!" })
return profiles
}
}

View file

@ -0,0 +1,106 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.app.Dialog
import android.view.View
import android.widget.TextView
import androidx.annotation.StyleRes
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfileName
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfiles
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import kotlinx.android.synthetic.main.player_quality_profile_dialog.*
class QualityProfileDialog(
val activity: FragmentActivity,
@StyleRes val themeRes: Int,
private val links: List<ExtractorLink>,
private val usedProfile: Int,
private val profileSelectionCallback: (QualityDataHelper.QualityProfile) -> Unit
) : Dialog(activity, themeRes) {
override fun show() {
setContentView(R.layout.player_quality_profile_dialog)
val profilesRecyclerView: RecyclerView = profiles_recyclerview
val useBtt: View = use_btt
val editBtt: View = edit_btt
val cancelBtt: View = cancel_btt
val defaultBtt: View = set_default_btt
val currentProfileText: TextView = currently_selected_profile_text
val selectedItemActionsHolder: View = selected_item_holder
fun getCurrentProfile(): QualityDataHelper.QualityProfile? {
return (profilesRecyclerView.adapter as? ProfilesAdapter)?.getCurrentProfile()
}
fun refreshProfiles() {
currentProfileText.text = getProfileName(usedProfile).asString(context)
(profilesRecyclerView.adapter as? ProfilesAdapter)?.updateList(getProfiles())
}
profilesRecyclerView.adapter = ProfilesAdapter(
mutableListOf(),
usedProfile,
) { oldIndex: Int?, newIndex: Int ->
profilesRecyclerView.adapter?.notifyItemChanged(newIndex)
selectedItemActionsHolder.alpha = 1f
if (oldIndex != null) {
profilesRecyclerView.adapter?.notifyItemChanged(oldIndex)
}
}
refreshProfiles()
editBtt.setOnClickListener {
getCurrentProfile()?.let { profile ->
SourcePriorityDialog(context, themeRes, links, profile) {
refreshProfiles()
}.show()
}
}
defaultBtt.setOnClickListener {
val currentProfile = getCurrentProfile() ?: return@setOnClickListener
val choices = QualityDataHelper.QualityProfileType.values()
.filter { it != QualityDataHelper.QualityProfileType.None }
val choiceNames = choices.map { txt(it.stringRes).asString(context) }
activity.showBottomDialog(
choiceNames,
choices.indexOf(currentProfile.type),
txt(R.string.set_default).asString(context),
false,
{},
{ index ->
val pickedChoice = choices.getOrNull(index) ?: return@showBottomDialog
// Remove previous picks
if (pickedChoice.unique) {
getProfiles().filter { it.type == pickedChoice }.forEach {
QualityDataHelper.setQualityProfileType(it.id, null)
}
}
QualityDataHelper.setQualityProfileType(currentProfile.id, pickedChoice)
refreshProfiles()
})
}
cancelBtt.setOnClickListener {
this.dismissSafe()
}
useBtt.setOnClickListener {
getCurrentProfile()?.let {
profileSelectionCallback.invoke(it)
this.dismissSafe()
}
}
super.show()
}
}

View file

@ -0,0 +1,105 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.app.Dialog
import android.content.Context
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.StyleRes
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.work.impl.constraints.controllers.ConstraintController
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import kotlinx.android.synthetic.main.player_select_source_priority.*
class SourcePriorityDialog(
ctx: Context,
@StyleRes themeRes: Int,
val links: List<ExtractorLink>,
private val profile: QualityDataHelper.QualityProfile,
/**
* Notify that the profile overview should be updated, for example if the name has been updated
* Should not be called excessively.
**/
private val updatedCallback: () -> Unit
) : Dialog(ctx, themeRes) {
override fun show() {
setContentView(R.layout.player_select_source_priority)
val sourcesRecyclerView: RecyclerView = sort_sources
val qualitiesRecyclerView: RecyclerView = sort_qualities
val profileText: EditText = profile_text_editable
val saveBtt: View = save_btt
val exitBtt: View = close_btt
val helpBtt: View = help_btt
profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context))
profileText.hint = txt(R.string.profile_number, profile.id).asString(context)
sourcesRecyclerView.adapter = PriorityAdapter(
links.map { link ->
SourcePriority(
null,
link.source,
QualityDataHelper.getSourcePriority(profile.id, link.source)
)
}.distinctBy { it.name }.sortedBy { -it.priority }.toMutableList()
)
qualitiesRecyclerView.adapter = PriorityAdapter(
Qualities.values().mapNotNull {
SourcePriority(
it,
Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null },
QualityDataHelper.getQualityPriority(profile.id, it)
)
}.sortedBy { -it.priority }.toMutableList()
)
@Suppress("UNCHECKED_CAST") // We know the types
saveBtt.setOnClickListener {
val qualityAdapter = qualitiesRecyclerView.adapter as? PriorityAdapter<Qualities>
val sourcesAdapter = sourcesRecyclerView.adapter as? PriorityAdapter<Nothing>
val qualities = qualityAdapter?.items ?: emptyList()
val sources = sourcesAdapter?.items ?: emptyList()
qualities.forEach {
val data = it.data as? Qualities ?: return@forEach
QualityDataHelper.setQualityPriority(profile.id, data, it.priority)
}
sources.forEach {
QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority)
}
qualityAdapter?.updateList(qualities.sortedBy { -it.priority })
sourcesAdapter?.updateList(sources.sortedBy { -it.priority })
val savedProfileName = profileText.text.toString()
if (savedProfileName.isBlank()) {
QualityDataHelper.setProfileName(profile.id, null)
} else {
QualityDataHelper.setProfileName(profile.id, savedProfileName)
}
updatedCallback.invoke()
}
exitBtt.setOnClickListener {
this.dismissSafe()
}
helpBtt.setOnClickListener {
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
setMessage(R.string.quality_profile_help)
}.show()
}
super.show()
}
}

View file

@ -72,7 +72,7 @@ sealed class UiImage {
fun ImageView?.setImage(value: UiImage?, fadeIn: Boolean = true) {
when (value) {
is UiImage.Image -> setImageImage(value,fadeIn)
is UiImage.Image -> setImageImage(value, fadeIn)
is UiImage.Drawable -> setImageDrawable(value)
null -> {
this?.isVisible = false
@ -88,7 +88,7 @@ fun ImageView?.setImageImage(value: UiImage.Image, fadeIn: Boolean = true) {
fun ImageView?.setImageDrawable(value: UiImage.Drawable) {
if (this == null) return
this.isVisible = true
setImageResource(value.resId)
this.setImage(UiImage.Drawable(value.resId))
}
@JvmName("imgNull")

View file

@ -117,7 +117,7 @@ object DataStoreHelper {
/**
* A datastore wide account for future implementations of a multiple account system
**/
private var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
fun getAllWatchStateIds(): List<Int>? {
val folder = "$currentAccount/$RESULT_WATCH_STATE"

View file

@ -114,16 +114,16 @@ data class ExtractorSubtitleLink(
*/
val schemaStripRegex = Regex("""^(https:|)//(www\.|)""")
enum class Qualities(var value: Int) {
Unknown(400),
P144(144), // 144p
P240(240), // 240p
P360(360), // 360p
P480(480), // 480p
P720(720), // 720p
P1080(1080), // 1080p
P1440(1440), // 1440p
P2160(2160); // 4k or 2160p
enum class Qualities(var value: Int, val defaultPriority: Int) {
Unknown(400, 4),
P144(144, 0), // 144p
P240(240, 2), // 240p
P360(360, 3), // 360p
P480(480, 4), // 480p
P720(720, 5), // 720p
P1080(1080, 6), // 1080p
P1440(1440, 7), // 1440p
P2160(2160, 8); // 4k or 2160p
companion object {
fun getStringByInt(qual: Int?): String {
@ -135,6 +135,14 @@ enum class Qualities(var value: Int) {
else -> "${qual}p"
}
}
fun getStringByIntFull(quality: Int): String {
return when (quality) {
0 -> "Auto"
Unknown.value -> "Unknown"
P2160.value -> "4K"
else -> "${quality}p"
}
}
}
}

View file

@ -250,17 +250,6 @@ object SingleSelectionHelper {
)
}
fun showBottomDialog(
items: List<String>,
selectedIndex: Int,
name: String,
showApply: Boolean,
dismissCallback: () -> Unit,
callback: (Int) -> Unit,
) {
}
/** Only for a low amount of items */
fun Activity?.showBottomDialog(
items: List<String>,

View file

@ -44,12 +44,13 @@ import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.bumptech.glide.request.target.Target
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.UiImage
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.GlideOptions.bitmapTransform
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlin.math.roundToInt
@ -188,11 +189,30 @@ object UIHelper {
fadeIn: Boolean = true,
colorCallback: ((Palette) -> Unit)? = null
): Boolean {
if (this == null || url.isNullOrBlank()) return false
if (url.isNullOrBlank()) return false
this.setImage(UiImage.Image(url, headers, errorImageDrawable), errorImageDrawable, fadeIn, colorCallback)
return true
}
fun ImageView?.setImage(
uiImage: UiImage?,
@DrawableRes
errorImageDrawable: Int? = null,
fadeIn: Boolean = true,
colorCallback: ((Palette) -> Unit)? = null
): Boolean {
if (this == null || uiImage == null) return false
val (glideImage, identifier) =
(uiImage as? UiImage.Drawable)?.resId?.let {
it to it.toString()
} ?: (uiImage as? UiImage.Image)?.let { image ->
GlideUrl(image.url) { image.headers ?: emptyMap() } to image.url
} ?: return false
return try {
val builder = GlideApp.with(this)
.load(GlideUrl(url) { headers ?: emptyMap() })
.load(glideImage)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.ALL).let { req ->
if (fadeIn)
@ -211,7 +231,13 @@ object UIHelper {
isFirstResource: Boolean
): Boolean {
resource?.toBitmapOrNull()
?.let { bitmap -> createPaletteAsync(url, bitmap, colorCallback) }
?.let { bitmap ->
createPaletteAsync(
identifier,
bitmap,
colorCallback
)
}
return false
}

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/white" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/>
</vector>

View file

@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
android:tint="?attr/white">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13H5v-2h14v2z"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View file

@ -0,0 +1,48 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="15dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/priority_text"
style="@style/NoCheckLabel"
android:layout_width="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/subtract_button"
tools:text="hello" />
<ImageView
android:id="@+id/subtract_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/priority_number"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="10dp"
android:src="@drawable/baseline_remove_24" />
<TextView
android:id="@+id/priority_number"
style="@style/NoCheckLabel"
android:layout_width="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/add_button"
android:gravity="center"
android:minWidth="50dp"
android:textColor="?attr/textColor"
tools:text="1" />
<ImageView
android:id="@+id/add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="10dp"
android:src="@drawable/ic_baseline_add_24" />
</RelativeLayout>

View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/profile_text_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/profiles"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/currently_selected_profile_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textColor="?attr/textColor"
android:textSize="15sp"
tools:text="Profile 1" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/profiles_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/profile_button_bar"
android:layout_below="@+id/profile_text_bar"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="4"
tools:listitem="@layout/player_quality_profile_item" />
<LinearLayout
android:id="@+id/profile_button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="10dp"
android:animateLayoutChanges="true"
android:gravity="end|bottom"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/selected_item_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:alpha="0.5">
<com.google.android.material.button.MaterialButton
android:id="@+id/edit_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:text="@string/edit" />
<com.google.android.material.button.MaterialButton
android:id="@+id/set_default_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:text="@string/set_default" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end">
<com.google.android.material.button.MaterialButton
android:id="@+id/use_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:text="@string/use" />
</LinearLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/cancel_btt"
style="@style/BlackButton"
android:layout_width="wrap_content"
android:text="@string/sort_cancel" />
</LinearLayout>
</RelativeLayout>

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
android:layout_height="match_parent"
android:focusable="false">
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1"
android:layout_marginStart="10dp"
android:animateLayoutChanges="true"
android:backgroundTint="?attr/primaryGrayBackground"
android:foreground="?attr/selectableItemBackgroundBorderless"
app:cardCornerRadius="@dimen/rounded_image_radius"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/profile_image_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.4"
android:contentDescription="@string/profile_background_des"
android:scaleType="centerCrop" />
<View
android:id="@+id/outline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/outline"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/profile_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="10dp"
android:textSize="16sp"
tools:text="@string/mobile_data" />
<TextView
android:id="@+id/text_is_wifi"
style="@style/DubButton"
android:layout_gravity="end"
android:text="@string/wifi"
android:textColor="@color/textColor" />
<TextView
android:id="@+id/text_is_mobile_data"
style="@style/DubButton"
android:layout_gravity="end"
android:text="@string/mobile_data"
android:textColor="@color/textColor" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -20,19 +21,44 @@
android:layout_weight="50"
android:orientation="vertical">
<TextView
<LinearLayout
android:id="@+id/profiles_click_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/pick_source"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
android:background="@drawable/outline_drawable_less"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/pick_source"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/source_settings_btt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:drawablePadding="10dp"
android:gravity="center"
android:minWidth="140dp"
android:paddingHorizontal="10dp"
android:textColor="?attr/textColor"
android:textSize="15sp"
app:drawableEndCompat="@drawable/ic_outline_settings_24"
tools:text="@string/profile_number" />
</LinearLayout>
<ListView
android:id="@+id/sort_providers"

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"
android:baselineAligned="false"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/sort_sources_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/pick_source"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sort_sources"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:background="?attr/primaryBlackBackground"
android:listSelector="@drawable/outline_drawable_less"
android:nextFocusLeft="@id/sort_subtitles"
android:nextFocusRight="@id/apply_btt"
android:requiresFadingEdge="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:layout_height="100dp"
tools:listitem="@layout/player_prioritize_item" />
</LinearLayout>
<LinearLayout
android:id="@+id/sort_subtitles_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:orientation="vertical">
<!-- android:id="@+id/subs_settings" android:foreground="?android:attr/selectableItemBackgroundBorderless"
-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<LinearLayout
android:id="@+id/subtitles_click_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/qualities"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/help_btt"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="12dp"
android:src="@drawable/baseline_help_outline_24"
android:contentDescription="@string/help" />
</LinearLayout>
<ImageView
android:layout_width="25dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginTop="0dp"
android:layout_marginEnd="10dp"
android:contentDescription="@string/home_change_provider_img_des"
android:src="@drawable/ic_outline_settings_24"
android:visibility="gone" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sort_qualities"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:background="?attr/primaryBlackBackground"
android:listSelector="@drawable/outline_drawable_less"
android:nextFocusLeft="@id/sort_providers"
android:nextFocusRight="@id/cancel_btt"
android:requiresFadingEdge="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:layout_height="200dp"
tools:listfooter="@layout/sort_bottom_footer_add_choice"
tools:listitem="@layout/player_prioritize_item" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/apply_btt_holder"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:layout_marginTop="-60dp"
android:orientation="horizontal">
<EditText
android:id="@+id/profile_text_editable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:inputType="text"
android:maxLength="32"
android:layout_marginHorizontal="?android:attr/listPreferredItemPaddingStart"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold"
tools:text="@string/profile_number"
android:autofillHints="username" />
<!-- <ImageView-->
<!-- android:layout_width="50dp"-->
<!-- android:layout_height="50dp"-->
<!-- android:padding="10dp"-->
<!-- android:layout_gravity="center"-->
<!-- android:src="@drawable/outline_edit_24" />-->
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
</Space>
<com.google.android.material.button.MaterialButton
android:id="@+id/save_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_save" />
<com.google.android.material.button.MaterialButton
android:id="@+id/close_btt"
style="@style/BlackButton"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_close" />
</LinearLayout>
</LinearLayout>

View file

@ -658,4 +658,23 @@
<string name="subscription_new">Subscribed to %s</string>
<string name="subscription_deleted">Unsubscribed from %s</string>
<string name="subscription_episode_released">Episode %d released!</string>
<string name="profile_number">Profile %d</string>
<string name="wifi">Wi-Fi</string>
<string name="mobile_data">Mobile data</string>
<string name="set_default">Set default</string>
<string name="use">Use</string>
<string name="edit">Edit</string>
<string name="profiles">Profiles</string>
<string name="help">Help</string>
<string name="quality_profile_help">
Here you can change how the sources are ordered. If a video has a higher priority it will appear higher in the source selection.
The sum of the source priority and the quality priority is the video priority.
\n\nSource A: 3
\nQuality B: 7
\nWill have a combined video priority of 10.
\n\nNOTE: If the sum is 10 or more the player will automatically skip loading when that link is loaded!
</string>
<string name="qualities">Qualities</string>
<string name="profile_background_des">Profile background</string>
</resources>

View file

@ -11,14 +11,14 @@
<PreferenceCategory
android:title="@string/pref_category_defaults">
<Preference
android:icon="@drawable/ic_baseline_hd_24"
android:key="@string/quality_pref_key"
android:title="@string/watch_quality_pref" />
<Preference
android:icon="@drawable/ic_baseline_hd_24"
android:key="@string/quality_pref_mobile_data_key"
android:title="@string/watch_quality_pref_data" />
<!-- <Preference-->
<!-- android:icon="@drawable/ic_baseline_hd_24"-->
<!-- android:key="@string/quality_pref_key"-->
<!-- android:title="@string/watch_quality_pref" />-->
<!-- <Preference-->
<!-- android:icon="@drawable/ic_baseline_hd_24"-->
<!-- android:key="@string/quality_pref_mobile_data_key"-->
<!-- android:title="@string/watch_quality_pref_data" />-->
<Preference
android:icon="@drawable/netflix_play"