Merge remote-tracking branch 'origin/master' into dialog2

This commit is contained in:
IndusAryan 2024-06-17 15:25:09 +05:30
commit 7f541a89aa
29 changed files with 403 additions and 382 deletions

View file

@ -164,7 +164,7 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// Android Core & Lifecycle // Android Core & Lifecycle
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7") implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
@ -174,7 +174,7 @@ dependencies {
// Design & UI // Design & UI
implementation("jp.wasabeef:glide-transformations:4.3.0") implementation("jp.wasabeef:glide-transformations:4.3.0")
implementation("androidx.preference:preference-ktx:1.2.1") implementation("androidx.preference:preference-ktx:1.2.1")
implementation("com.google.android.material:material:1.11.0") implementation("com.google.android.material:material:1.12.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
@ -185,7 +185,7 @@ dependencies {
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP // For KSP -> Official Annotation Processors are Not Yet Supported for KSP
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
implementation("com.google.guava:guava:32.1.3-android") implementation("com.google.guava:guava:33.2.0-android")
implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
// Media 3 (ExoPlayer) // Media 3 (ExoPlayer)
@ -202,7 +202,7 @@ dependencies {
// PlayBack // PlayBack
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
implementation("com.github.TeamNewPipe.NewPipeExtractor:NewPipeExtractor:6dc25f7b97") /* For Trailers implementation("com.github.teamnewpipe:NewPipeExtractor:fafd471") /* For Trailers
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */ ^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding
@ -219,9 +219,7 @@ dependencies {
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
// Extensions & Other Libs // Extensions & Other Libs
implementation("org.mozilla:rhino:1.7.13") /* run JavaScript implementation("org.mozilla:rhino:1.7.15") // run JavaScript
^ Don't Bump RhinoJS to 1.7.14,`NoClassDefFoundError` Occurs and Trailers won't play (even with Desugaring)
NewPipeExtractor Issue */
implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance
implementation("com.github.LagradOst:SafeFile:0.0.6") // To Prevent the URI File Fu*kery implementation("com.github.LagradOst:SafeFile:0.0.6") // To Prevent the URI File Fu*kery
implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9 implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9

View file

@ -651,7 +651,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
} }
override fun dispatchKeyEvent(event: KeyEvent?): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val response = CommonActivity.dispatchKeyEvent(this, event) val response = CommonActivity.dispatchKeyEvent(this, event)
if (response != null) if (response != null)
return response return response

View file

@ -0,0 +1,37 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
class GoodstreamExtractor : ExtractorApi() {
override var name = "Goodstream"
override val mainUrl = "https://goodstream.uno"
override val requiresReferer = false
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
app.get(url).document.select("script").map { script ->
if (script.data().contains(Regex("file|player"))) {
val urlRegex = Regex("file: \"(https:\\/\\/[a-z0-9.\\/-_?=&]+)\",")
urlRegex.find(script.data())?.groupValues?.get(1).let { link ->
callback.invoke(
ExtractorLink(
name,
name,
link!!,
mainUrl,
Qualities.Unknown.value,
)
)
}
}
}
}
}

View file

@ -21,3 +21,8 @@ class FourPlayRu : ContentX() {
override var name = "FourPlayRu" override var name = "FourPlayRu"
override var mainUrl = "https://four.playru.net" override var mainUrl = "https://four.playru.net"
} }
class FourPichive : ContentX() {
override var name = "FourPichive"
override var mainUrl = "https://four.pichive.online"
}

View file

@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import java.net.URLDecoder import java.net.URLDecoder
@ -26,6 +27,7 @@ class VidSrcTo : ExtractorApi() {
val res = app.get("$mainUrl/ajax/embed/episode/$mediaId/sources").parsedSafe<VidsrctoEpisodeSources>() ?: return val res = app.get("$mainUrl/ajax/embed/episode/$mediaId/sources").parsedSafe<VidsrctoEpisodeSources>() ?: return
if (res.status != 200) return if (res.status != 200) return
res.result?.amap { source -> res.result?.amap { source ->
try {
val embedRes = app.get("$mainUrl/ajax/embed/source/${source.id}").parsedSafe<VidsrctoEmbedSource>() ?: return@amap val embedRes = app.get("$mainUrl/ajax/embed/source/${source.id}").parsedSafe<VidsrctoEmbedSource>() ?: return@amap
val finalUrl = DecryptUrl(embedRes.result.encUrl) val finalUrl = DecryptUrl(embedRes.result.encUrl)
if(finalUrl.equals(embedRes.result.encUrl)) return@amap if(finalUrl.equals(embedRes.result.encUrl)) return@amap
@ -33,6 +35,9 @@ class VidSrcTo : ExtractorApi() {
"Vidplay" -> AnyVidplay(finalUrl.substringBefore("/e/")).getUrl(finalUrl, referer, subtitleCallback, callback) "Vidplay" -> AnyVidplay(finalUrl.substringBefore("/e/")).getUrl(finalUrl, referer, subtitleCallback, callback)
"Filemoon" -> FileMoon().getUrl(finalUrl, referer, subtitleCallback, callback) "Filemoon" -> FileMoon().getUrl(finalUrl, referer, subtitleCallback, callback)
} }
} catch (e: Exception) {
logError(e)
}
} }
} }

View file

@ -166,18 +166,10 @@ open class TraktProvider : MainAPI() {
val resSeasons = getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=cloud9,full,episodes") val resSeasons = getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=cloud9,full,episodes")
val episodes = mutableListOf<Episode>() val episodes = mutableListOf<Episode>()
val seasons = parseJson<List<Seasons>>(resSeasons) val seasons = parseJson<List<Seasons>>(resSeasons)
val seasonsNames = mutableListOf<SeasonData>()
var nextAir: NextAiring? = null var nextAir: NextAiring? = null
seasons.forEach { season -> seasons.forEach { season ->
seasonsNames.add(
SeasonData(
season.number!!,
season.title
)
)
season.episodes?.map { episode -> season.episodes?.map { episode ->
val linkData = LinkData( val linkData = LinkData(
@ -250,7 +242,6 @@ open class TraktProvider : MainAPI() {
this.comingSoon = isUpcoming(mediaDetails.released) this.comingSoon = isUpcoming(mediaDetails.released)
//posterHeaders //posterHeaders
this.nextAiring = nextAir this.nextAiring = nextAir
this.seasonNames = seasonsNames
this.backgroundPosterUrl = getOriginalWidthImageUrl(backDropUrl) this.backgroundPosterUrl = getOriginalWidthImageUrl(backDropUrl)
this.contentRating = mediaDetails.certification this.contentRating = mediaDetails.certification
addTrailer(mediaDetails.trailer) addTrailer(mediaDetails.trailer)

View file

@ -12,9 +12,8 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
val aniListApi = AniListApi(0) val aniListApi = AniListApi(0)
val openSubtitlesApi = OpenSubtitlesApi(0) val openSubtitlesApi = OpenSubtitlesApi(0)
val simklApi = SimklApi(0) val simklApi = SimklApi(0)
val indexSubtitlesApi = IndexSubtitleApi()
val addic7ed = Addic7ed() val addic7ed = Addic7ed()
val subDl = SubDL() val subDlApi = SubDlApi(0)
val localListApi = LocalList() val localListApi = LocalList()
// used to login via app intent // used to login via app intent
@ -26,7 +25,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
// this needs init with context and can be accessed in settings // this needs init with context and can be accessed in settings
val accountManagers val accountManagers
get() = listOf( get() = listOf(
malApi, aniListApi, openSubtitlesApi, simklApi //nginxApi malApi, aniListApi, openSubtitlesApi, subDlApi, simklApi //nginxApi
) )
// used for active syncing // used for active syncing
@ -36,14 +35,16 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
) )
val inAppAuths val inAppAuths
get() = listOf(openSubtitlesApi)//, nginxApi) get() = listOf<InAppAuthAPIManager>(
openSubtitlesApi,
subDlApi
)//, nginxApi)
val subtitleProviders val subtitleProviders
get() = listOf( get() = listOf(
openSubtitlesApi, openSubtitlesApi,
indexSubtitlesApi, // they got anti scraping measures in place :(
addic7ed, addic7ed,
subDl subDlApi
) )
const val appString = "cloudstreamapp" const val appString = "cloudstreamapp"

View file

@ -1,265 +0,0 @@
package com.lagradost.cloudstream3.syncproviders.providers
import android.util.Log
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.imdbUrlToIdNullable
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.utils.SubtitleHelper
class IndexSubtitleApi : AbstractSubApi {
override val name = "IndexSubtitle"
override val idPrefix = "indexsubtitle"
override val requiresLogin = false
override val icon: Nothing? = null
override val createAccountUrl: Nothing? = null
override fun loginInfo(): Nothing? = null
override fun logOut() {}
companion object {
const val host = "https://indexsubtitle.com"
const val TAG = "INDEXSUBS"
fun getOrdinal(num: Int?): String? {
return when (num) {
1 -> "First"
2 -> "Second"
3 -> "Third"
4 -> "Fourth"
5 -> "Fifth"
6 -> "Sixth"
7 -> "Seventh"
8 -> "Eighth"
9 -> "Ninth"
10 -> "Tenth"
11 -> "Eleventh"
12 -> "Twelfth"
13 -> "Thirteenth"
14 -> "Fourteenth"
15 -> "Fifteenth"
16 -> "Sixteenth"
17 -> "Seventeenth"
18 -> "Eighteenth"
19 -> "Nineteenth"
20 -> "Twentieth"
21 -> "Twenty-First"
22 -> "Twenty-Second"
23 -> "Twenty-Third"
24 -> "Twenty-Fourth"
25 -> "Twenty-Fifth"
26 -> "Twenty-Sixth"
27 -> "Twenty-Seventh"
28 -> "Twenty-Eighth"
29 -> "Twenty-Ninth"
30 -> "Thirtieth"
31 -> "Thirty-First"
32 -> "Thirty-Second"
33 -> "Thirty-Third"
34 -> "Thirty-Fourth"
35 -> "Thirty-Fifth"
else -> null
}
}
}
private fun fixUrl(url: String): String {
if (url.startsWith("http")) {
return url
}
if (url.isEmpty()) {
return ""
}
val startsWithNoHttp = url.startsWith("//")
if (startsWithNoHttp) {
return "https:$url"
} else {
if (url.startsWith('/')) {
return host + url
}
return "$host/$url"
}
}
private fun isRightEps(text: String, seasonNum: Int?, epNum: Int?): Boolean {
val FILTER_EPS_REGEX =
Regex("(?i)((Chapter\\s?0?${epNum})|((Season)?\\s?0?${seasonNum}?\\s?(Episode)\\s?0?${epNum}[^0-9]))|(?i)((S?0?${seasonNum}?E0?${epNum}[^0-9])|(0?${seasonNum}[a-z]0?${epNum}[^0-9]))")
return text.contains(FILTER_EPS_REGEX)
}
private fun haveEps(text: String): Boolean {
val HAVE_EPS_REGEX =
Regex("(?i)((Chapter\\s?0?\\d)|((Season)?\\s?0?\\d?\\s?(Episode)\\s?0?\\d))|(?i)((S?0?\\d?E0?\\d)|(0?\\d[a-z]0?\\d))")
return text.contains(HAVE_EPS_REGEX)
}
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity> {
val imdbId = query.imdbId?.replace("tt", "")?.toLong() ?: 0
val lang = query.lang
val queryLang = SubtitleHelper.fromTwoLettersToLanguage(lang.toString())
val queryText = query.query
val epNum = query.epNumber ?: 0
val seasonNum = query.seasonNumber ?: 0
val yearNum = query.year ?: 0
val urlItems = ArrayList<String>()
fun cleanResources(
results: MutableList<AbstractSubtitleEntities.SubtitleEntity>,
name: String,
link: String
) {
results.add(
AbstractSubtitleEntities.SubtitleEntity(
idPrefix = idPrefix,
name = name,
lang = queryLang.toString(),
data = link,
source = this.name,
type = if (seasonNum > 0) TvType.TvSeries else TvType.Movie,
epNumber = epNum,
seasonNumber = seasonNum,
year = yearNum,
)
)
}
val document = app.get("$host/?search=$queryText").document
document.select("div.my-3.p-3 div.media").map { block ->
if (seasonNum > 0) {
val name = block.select("strong.text-primary, strong.text-info").text().trim()
val season = getOrdinal(seasonNum)
if ((block.selectFirst("a")?.attr("href")
?.contains(
"$season",
ignoreCase = true
)!! || name.contains(
"$season",
ignoreCase = true
)) && name.contains(queryText, ignoreCase = true)
) {
block.select("div.media").mapNotNull {
urlItems.add(
fixUrl(
it.selectFirst("a")!!.attr("href")
)
)
}
}
} else {
if (block.selectFirst("strong")!!.text().trim()
.matches(Regex("(?i)^$queryText\$"))
) {
if (block.select("span[title=Release]").isNullOrEmpty()) {
block.select("div.media").mapNotNull {
val urlItem = fixUrl(
it.selectFirst("a")!!.attr("href")
)
val itemDoc = app.get(urlItem).document
val id = imdbUrlToIdNullable(
itemDoc.selectFirst("div.d-flex span.badge.badge-primary")?.parent()
?.attr("href")
)?.toLongOrNull()
val year = itemDoc.selectFirst("div.d-flex span.badge.badge-success")
?.ownText()
?.trim().toString()
Log.i(TAG, "id => $id \nyear => $year||$yearNum")
if (imdbId > 0) {
if (id == imdbId) {
urlItems.add(urlItem)
}
} else {
if (year.contains("$yearNum")) {
urlItems.add(urlItem)
}
}
}
} else {
if (block.select("span[title=Release]").text().trim()
.contains("$yearNum")
) {
block.select("div.media").mapNotNull {
urlItems.add(
fixUrl(
it.selectFirst("a")!!.attr("href")
)
)
}
}
}
}
}
}
Log.i(TAG, "urlItems => $urlItems")
val results = mutableListOf<AbstractSubtitleEntities.SubtitleEntity>()
urlItems.forEach { url ->
val request = app.get(url)
if (request.isSuccessful) {
request.document.select("div.my-3.p-3 div.media").map { block ->
if (block.select("span.d-block span[data-original-title=Language]").text()
.trim()
.contains("$queryLang")
) {
var name = block.select("strong.text-primary, strong.text-info").text().trim()
val link = fixUrl(block.selectFirst("a")!!.attr("href"))
if (seasonNum > 0) {
when {
isRightEps(name, seasonNum, epNum) -> {
cleanResources(results, name, link)
}
!(haveEps(name)) -> {
name = "$name (S${seasonNum}:E${epNum})"
cleanResources(results, name, link)
}
}
} else {
cleanResources(results, name, link)
}
}
}
}
}
return results
}
override suspend fun load(data: AbstractSubtitleEntities.SubtitleEntity): String? {
val seasonNum = data.seasonNumber
val epNum = data.epNumber
val req = app.get(data.data)
if (req.isSuccessful) {
val document = req.document
val link = if (document.select("div.my-3.p-3 div.media").size == 1) {
fixUrl(
document.selectFirst("div.my-3.p-3 div.media a")!!.attr("href")
)
} else {
document.select("div.my-3.p-3 div.media").firstNotNullOf { block ->
val name =
block.selectFirst("strong.d-block")?.text()?.trim().toString()
if (seasonNum!! > 0) {
if (isRightEps(name, seasonNum, epNum)) {
fixUrl(block.selectFirst("a")!!.attr("href"))
} else {
null
}
} else {
fixUrl(block.selectFirst("a")!!.attr("href"))
}
}
}
return link
}
return null
}
}

View file

@ -1,21 +1,80 @@
package com.lagradost.cloudstream3.syncproviders.providers package com.lagradost.cloudstream3.syncproviders.providers
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
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.ErrorLoadingException
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.subtitles.AbstractSubProvider import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.subtitles.SubtitleResource import com.lagradost.cloudstream3.subtitles.SubtitleResource
import com.lagradost.cloudstream3.syncproviders.AuthAPI.LoginInfo
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager
class SubDL : AbstractSubProvider { class SubDlApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi {
//API Documentation: https://subdl.com/api-doc
val mainUrl = "https://subdl.com/"
val name = "SubDL"
override val idPrefix = "subdl" override val idPrefix = "subdl"
override val name = "SubDL"
override val icon = R.drawable.subdl_logo_big
override val requiresPassword = true
override val requiresEmail = true
override val createAccountUrl = "https://subdl.com/login"
companion object { companion object {
const val APIKEY = "zRJl5QA-8jNA2i0pE8cxANbEukANp7IM" const val APIURL = "https://api.subdl.com"
const val APIENDPOINT = "https://api.subdl.com/api/v1/subtitles" const val APIENDPOINT = "$APIURL/api/v1/subtitles"
const val DOWNLOADENDPOINT = "https://dl.subdl.com" const val DOWNLOADENDPOINT = "https://dl.subdl.com"
const val SUBDL_SUBTITLES_USER_KEY: String = "subdl_user"
var currentSession: SubtitleOAuthEntity? = null
}
override suspend fun initialize() {
currentSession = getAuthKey()
}
override fun logOut() {
setAuthKey(null)
removeAccountKeys()
currentSession = getAuthKey()
}
override suspend fun login(data: InAppAuthAPI.LoginData): Boolean {
val email = data.email ?: throw ErrorLoadingException("Requires Email")
val password = data.password ?: throw ErrorLoadingException("Requires Password")
switchToNewAccount()
try {
if (initLogin(email, password)) {
registerAccount()
return true
}
} catch (e: Exception) {
logError(e)
switchToOldAccount()
}
switchToOldAccount()
return false
}
override fun getLatestLoginData(): InAppAuthAPI.LoginData? {
val current = getAuthKey() ?: return null
return InAppAuthAPI.LoginData(
email = current.userEmail,
password = current.pass
)
}
override fun loginInfo(): LoginInfo? {
getAuthKey()?.let { user ->
return LoginInfo(
profilePicture = null,
name = user.name ?: user.userEmail,
accountIndex = accountIndex
)
}
return null
} }
override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? { override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List<AbstractSubtitleEntities.SubtitleEntity>? {
@ -37,8 +96,8 @@ class SubDL : AbstractSubProvider {
val searchQueryUrl = when (idQuery) { val searchQueryUrl = when (idQuery) {
//Use imdb/tmdb id to search if its valid //Use imdb/tmdb id to search if its valid
null -> "$APIENDPOINT?api_key=$APIKEY&film_name=$queryText&languages=${query.lang}$epQuery$seasonQuery$yearQuery" null -> "$APIENDPOINT?api_key=${currentSession?.apiKey}&film_name=$queryText&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
else -> "$APIENDPOINT?api_key=$APIKEY$idQuery&languages=${query.lang}$epQuery$seasonQuery$yearQuery" else -> "$APIENDPOINT?api_key=${currentSession?.apiKey}$idQuery&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
} }
val req = app.get( val req = app.get(
@ -49,7 +108,7 @@ class SubDL : AbstractSubProvider {
) )
return req.parsedSafe<ApiResponse>()?.subtitles?.map { subtitle -> return req.parsedSafe<ApiResponse>()?.subtitles?.map { subtitle ->
val name = subtitle.releaseName
val lang = subtitle.lang.replaceFirstChar { it.uppercase() } val lang = subtitle.lang.replaceFirstChar { it.uppercase() }
val resEpNum = subtitle.episode ?: query.epNumber val resEpNum = subtitle.episode ?: query.epNumber
val resSeasonNum = subtitle.season ?: query.seasonNumber val resSeasonNum = subtitle.season ?: query.seasonNumber
@ -57,13 +116,14 @@ class SubDL : AbstractSubProvider {
AbstractSubtitleEntities.SubtitleEntity( AbstractSubtitleEntities.SubtitleEntity(
idPrefix = this.idPrefix, idPrefix = this.idPrefix,
name = name, name = subtitle.releaseName,
lang = lang, lang = lang,
data = "${DOWNLOADENDPOINT}${subtitle.url}", data = "${DOWNLOADENDPOINT}${subtitle.url}",
type = type, type = type,
source = this.name, source = this.name,
epNumber = resEpNum, epNumber = resEpNum,
seasonNumber = resSeasonNum, seasonNumber = resSeasonNum,
isHearingImpaired = subtitle.hearingImpaired ?: false,
) )
} }
} }
@ -74,6 +134,88 @@ class SubDL : AbstractSubProvider {
} }
} }
private suspend fun initLogin(useremail: String, password: String): Boolean {
val tokenResponse = app.post(
url = "$APIURL/login",
data = mapOf(
"email" to useremail,
"password" to password
)
).parsedSafe<OAuthTokenResponse>()
if (tokenResponse?.token == null) return false
val apiResponse = app.get(
url = "$APIURL/user/userApi",
headers = mapOf(
"Authorization" to "Bearer ${tokenResponse.token}"
)
).parsedSafe<ApiKeyResponse>()
if (apiResponse?.ok == false) return false
setAuthKey(
SubtitleOAuthEntity(
userEmail = useremail,
pass = password,
name = tokenResponse.userData?.username ?: tokenResponse.userData?.name,
accessToken = tokenResponse.token,
apiKey = apiResponse?.apiKey
)
)
return true
}
private fun getAuthKey(): SubtitleOAuthEntity? {
return getKey(accountId, SUBDL_SUBTITLES_USER_KEY)
}
private fun setAuthKey(data: SubtitleOAuthEntity?) {
if (data == null) removeKey(
accountId,
SUBDL_SUBTITLES_USER_KEY
)
currentSession = data
setKey(accountId, SUBDL_SUBTITLES_USER_KEY, data)
}
data class SubtitleOAuthEntity(
@JsonProperty("userEmail") var userEmail: String,
@JsonProperty("pass") var pass: String,
@JsonProperty("name") var name: String? = null,
@JsonProperty("accessToken") var accessToken: String? = null,
@JsonProperty("apiKey") var apiKey: String? = null,
)
data class OAuthTokenResponse(
@JsonProperty("token") val token: String? = null,
@JsonProperty("userData") val userData: UserData? = null,
@JsonProperty("status") val status: Boolean? = null,
@JsonProperty("message") val message: String? = null,
)
data class UserData(
@JsonProperty("email") val email: String,
@JsonProperty("name") val name: String,
@JsonProperty("country") val country: String,
@JsonProperty("scStepCode") val scStepCode: String,
@JsonProperty("scVerified") val scVerified: Boolean,
@JsonProperty("username") val username: String? = null,
@JsonProperty("scUsername") val scUsername: String,
)
data class ApiKeyResponse(
@JsonProperty("ok") val ok: Boolean? = false,
@JsonProperty("api_key") val apiKey: String? = null,
@JsonProperty("usage") val usage: Usage? = null,
)
data class Usage(
@JsonProperty("total") val total: Long? = 0,
@JsonProperty("today") val today: Long? = 0,
)
data class ApiResponse( data class ApiResponse(
@JsonProperty("status") val status: Boolean? = null, @JsonProperty("status") val status: Boolean? = null,
@JsonProperty("results") val results: List<Result>? = null, @JsonProperty("results") val results: List<Result>? = null,
@ -96,7 +238,10 @@ class SubDL : AbstractSubProvider {
@JsonProperty("lang") val lang: String, @JsonProperty("lang") val lang: String,
@JsonProperty("author") val author: String? = null, @JsonProperty("author") val author: String? = null,
@JsonProperty("url") val url: String? = null, @JsonProperty("url") val url: String? = null,
@JsonProperty("subtitlePage") val subtitlePage: String? = null,
@JsonProperty("season") val season: Int? = null, @JsonProperty("season") val season: Int? = null,
@JsonProperty("episode") val episode: Int? = null, @JsonProperty("episode") val episode: Int? = null,
@JsonProperty("language") val language: String? = null,
@JsonProperty("hi") val hearingImpaired: Boolean? = null,
) )
} }

View file

@ -17,7 +17,7 @@ import com.lagradost.safefile.SafeFile
const val DTAG = "PlayerActivity" const val DTAG = "PlayerActivity"
class DownloadedPlayerActivity : AppCompatActivity() { class DownloadedPlayerActivity : AppCompatActivity() {
override fun dispatchKeyEvent(event: KeyEvent?): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
CommonActivity.dispatchKeyEvent(this, event)?.let { CommonActivity.dispatchKeyEvent(this, event)?.let {
return it return it
} }

View file

@ -1728,7 +1728,7 @@ class ResultViewModel2 : ViewModel() {
txt(R.string.episode_action_cast_mirror) txt(R.string.episode_action_cast_mirror)
) { (result, index) -> ) { (result, index) ->
val host = device?.host ?: return@acquireSingleLink val host = device?.host ?: return@acquireSingleLink
val link = result.links.firstOrNull() ?: return@acquireSingleLink val link = result.links.getOrNull(index) ?: return@acquireSingleLink
FcastSession(host).use { session -> FcastSession(host).use { session ->
session.sendMessage( session.sendMessage(

View file

@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniList
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.openSubtitlesApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.openSubtitlesApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlApi
import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
import com.lagradost.cloudstream3.syncproviders.OAuth2API import com.lagradost.cloudstream3.syncproviders.OAuth2API
@ -35,6 +36,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
@ -297,10 +299,10 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback {
hideKeyboard() hideKeyboard()
setPreferencesFromResource(R.xml.settings_account, rootKey) setPreferencesFromResource(R.xml.settings_account, rootKey)
// hide preference on tvs and emulators //Hides the security category on TV as it's only Biometric for now
getPref(R.string.biometric_key)?.isEnabled = isLayout(PHONE) getPref(R.string.pref_category_security_key)?.hideOn(TV or EMULATOR)
getPref(R.string.biometric_key)?.setOnPreferenceClickListener { getPref(R.string.biometric_key)?.hideOn(TV or EMULATOR)?.setOnPreferenceClickListener {
val ctx = context ?: return@setOnPreferenceClickListener false val ctx = context ?: return@setOnPreferenceClickListener false
if (deviceHasPasswordPinLock(ctx)) { if (deviceHasPasswordPinLock(ctx)) {
@ -324,12 +326,12 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback {
R.string.anilist_key to aniListApi, R.string.anilist_key to aniListApi,
R.string.simkl_key to simklApi, R.string.simkl_key to simklApi,
R.string.opensubtitles_key to openSubtitlesApi, R.string.opensubtitles_key to openSubtitlesApi,
R.string.subdl_key to subDlApi,
) )
for ((key, api) in syncApis) { for ((key, api) in syncApis) {
getPref(key)?.apply { getPref(key)?.apply {
title = title = api.name
getString(R.string.login_format).format(api.name, getString(R.string.account))
setOnPreferenceClickListener { setOnPreferenceClickListener {
val info = api.loginInfo() val info = api.loginInfo()
if (info != null) { if (info != null) {

View file

@ -26,6 +26,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
@ -53,6 +54,30 @@ class SettingsFragment : Fragment() {
} }
} }
/**
* Hide many Preferences on selected layouts.
**/
fun PreferenceFragmentCompat?.hidePrefs(ids: List<Int>, layoutFlags: Int) {
if (this == null) return
try {
ids.forEach {
getPref(it)?.isVisible = !isLayout(layoutFlags)
}
} catch (e: Exception) {
logError(e)
}
}
/**
* Hide the Preference on selected layouts.
**/
fun Preference?.hideOn(layoutFlags: Int): Preference? {
if (this == null) return null
this.isVisible = !isLayout(layoutFlags)
return this
}
/** /**
* On TV you cannot properly scroll to the bottom of settings, this fixes that. * On TV you cannot properly scroll to the bottom of settings, this fixes that.
* */ * */

View file

@ -27,10 +27,13 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.network.initClient
import com.lagradost.cloudstream3.ui.EasterEggMonke import com.lagradost.cloudstream3.ui.EasterEggMonke
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.beneneCount import com.lagradost.cloudstream3.ui.settings.Globals.beneneCount
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
@ -208,9 +211,7 @@ class SettingsGeneral : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
// disable preference on tvs and emulators getPref(R.string.battery_optimisation_key)?.hideOn(TV or EMULATOR)?.setOnPreferenceClickListener {
getPref(R.string.battery_optimisation_key)?.isEnabled = isLayout(PHONE)
getPref(R.string.battery_optimisation_key)?.setOnPreferenceClickListener {
val ctx = context ?: return@setOnPreferenceClickListener false val ctx = context ?: return@setOnPreferenceClickListener false
if (isAppRestricted(ctx)) { if (isAppRestricted(ctx)) {

View file

@ -7,8 +7,14 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hidePrefs
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
@ -31,6 +37,18 @@ class SettingsPlayer : PreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.settings_player, rootKey) setPreferencesFromResource(R.xml.settings_player, rootKey)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
//Hide specific prefs on TV/EMULATOR
hidePrefs(
listOf(
R.string.pref_category_gestures_key,
R.string.rotate_video_key,
R.string.auto_rotate_video_key
),
TV or EMULATOR
)
getPref(R.string.pref_category_android_tv_key)?.hideOn(PHONE)
getPref(R.string.video_buffer_length_key)?.setOnPreferenceClickListener { getPref(R.string.video_buffer_length_key)?.setOnPreferenceClickListener {
val prefNames = resources.getStringArray(R.array.video_buffer_length_names) val prefNames = resources.getStringArray(R.array.video_buffer_length_names)
val prefValues = resources.getIntArray(R.array.video_buffer_length_values) val prefValues = resources.getIntArray(R.array.video_buffer_length_values)
@ -227,6 +245,5 @@ class SettingsPlayer : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
} }
} }
} }

View file

@ -9,6 +9,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.amap
@ -181,8 +182,11 @@ class PluginsViewModel : ViewModel() {
} }
private suspend fun updatePluginListPrivate(context: Context, repositoryUrl: String) { private suspend fun updatePluginListPrivate(context: Context, repositoryUrl: String) {
val isAdult = settingsForProvider.enableAdult
val plugins = getPlugins(repositoryUrl) val plugins = getPlugins(repositoryUrl)
val list = plugins.map { plugin -> val list = plugins.filter {
return@filter !(it.second.tvTypes?.contains("NSFW") == true && !isAdult)
}.map { plugin ->
PluginViewData(plugin, isDownloaded(context, plugin.second.internalName, plugin.first)) PluginViewData(plugin, isDownloaded(context, plugin.second.internalName, plugin.first))
} }

View file

@ -26,6 +26,7 @@ import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_T
import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY
import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY
import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY
import com.lagradost.cloudstream3.syncproviders.providers.SubDlApi.Companion.SUBDL_SUBTITLES_USER_KEY
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
@ -64,6 +65,7 @@ object BackupUtils {
PLUGINS_KEY_LOCAL, PLUGINS_KEY_LOCAL,
OPEN_SUBTITLES_USER_KEY, OPEN_SUBTITLES_USER_KEY,
SUBDL_SUBTITLES_USER_KEY,
DOWNLOAD_EPISODE_CACHE, DOWNLOAD_EPISODE_CACHE,

View file

@ -67,6 +67,7 @@ import com.lagradost.cloudstream3.extractors.Gdriveplayerorg
import com.lagradost.cloudstream3.extractors.Gdriveplayerus import com.lagradost.cloudstream3.extractors.Gdriveplayerus
import com.lagradost.cloudstream3.extractors.Gofile import com.lagradost.cloudstream3.extractors.Gofile
import com.lagradost.cloudstream3.extractors.GuardareStream import com.lagradost.cloudstream3.extractors.GuardareStream
import com.lagradost.cloudstream3.extractors.GoodstreamExtractor
import com.lagradost.cloudstream3.extractors.Guccihide import com.lagradost.cloudstream3.extractors.Guccihide
import com.lagradost.cloudstream3.extractors.Hxfile import com.lagradost.cloudstream3.extractors.Hxfile
import com.lagradost.cloudstream3.extractors.JWPlayer import com.lagradost.cloudstream3.extractors.JWPlayer
@ -110,6 +111,7 @@ import com.lagradost.cloudstream3.extractors.Hotlinger
import com.lagradost.cloudstream3.extractors.FourCX import com.lagradost.cloudstream3.extractors.FourCX
import com.lagradost.cloudstream3.extractors.PlayRu import com.lagradost.cloudstream3.extractors.PlayRu
import com.lagradost.cloudstream3.extractors.FourPlayRu import com.lagradost.cloudstream3.extractors.FourPlayRu
import com.lagradost.cloudstream3.extractors.FourPichive
import com.lagradost.cloudstream3.extractors.HDMomPlayer import com.lagradost.cloudstream3.extractors.HDMomPlayer
import com.lagradost.cloudstream3.extractors.HDPlayerSystem import com.lagradost.cloudstream3.extractors.HDPlayerSystem
import com.lagradost.cloudstream3.extractors.VideoSeyred import com.lagradost.cloudstream3.extractors.VideoSeyred
@ -748,6 +750,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
FourCX(), FourCX(),
PlayRu(), PlayRu(),
FourPlayRu(), FourPlayRu(),
FourPichive(),
HDMomPlayer(), HDMomPlayer(),
HDPlayerSystem(), HDPlayerSystem(),
VideoSeyred(), VideoSeyred(),
@ -877,6 +880,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Gdriveplayerorg(), Gdriveplayerorg(),
Gdriveplayerus(), Gdriveplayerus(),
Gdriveplayerco(), Gdriveplayerco(),
GoodstreamExtractor(),
Gdriveplayer(), Gdriveplayer(),
DatabaseGdrive(), DatabaseGdrive(),
DatabaseGdrive2(), DatabaseGdrive2(),

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="20dp"
android:viewportHeight="320"
android:viewportWidth="320"
android:width="20dp">
<path android:fillColor="@color/white"
android:pathData="m107.87,39.3l-8.44,8.59l0,35.68l0,35.83l18.95,22.5c10.36,12.44 30.35,36.27 44.41,53l25.46,30.5l0,12.14l0,12.29l-24.43,-0l-24.43,-0l0,-11.84l0,-11.84l-19.99,-0l-19.99,-0l0,23.24l0,23.24l8.44,8.59l8.44,8.59l48.26,-0l48.26,-0l7.7,-7.85l7.7,-7.85l0,-36.86l-0.15,-37.01l-23.98,-28.28c-13.18,-15.54 -33.16,-39.23 -44.26,-52.55l-20.43,-24.13l0,-12.29l0,-12.29l24.43,-0l24.43,-0l0,12.58l0,12.58l19.99,-0l19.99,-0l0,-24.87l0,-24.87l-7.85,-7.7l-7.85,-7.7l-48.11,-0l-48.11,-0l-8.44,8.59z"/>
</vector>

View file

@ -64,12 +64,14 @@
<TextView <TextView
android:id="@+id/account_switch_account" android:id="@+id/account_switch_account"
android:text="@string/switch_account" android:text="@string/switch_account"
style="@style/SettingsItem" /> style="@style/SettingsItem"
android:focusable="true"/>
<TextView <TextView
android:id="@+id/account_logout" android:id="@+id/account_logout"
android:text="@string/logout" android:text="@string/logout"
style="@style/SettingsItem"> style="@style/SettingsItem"
android:focusable="true">
<requestFocus /> <requestFocus />
</TextView> </TextView>

View file

@ -4,7 +4,8 @@
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="match_parent"> android:layout_width="match_parent"
android:focusable="true">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/account_profile_picture_holder" android:id="@+id/account_profile_picture_holder"

View file

@ -13,12 +13,14 @@
tools:listitem="@layout/account_single" tools:listitem="@layout/account_single"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:focusable="true"/>
<TextView <TextView
android:id="@+id/account_add" android:id="@+id/account_add"
android:text="@string/add_account" android:text="@string/add_account"
style="@style/SettingsItem"> style="@style/SettingsItem"
android:focusable="true">
<requestFocus /> <requestFocus />
</TextView> </TextView>

View file

@ -107,6 +107,7 @@
android:layout_margin="10dp" android:layout_margin="10dp"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des" android:contentDescription="@string/change_providers_img_des"
android:focusable="true"
android:nextFocusLeft="@id/year_btt" android:nextFocusLeft="@id/year_btt"
android:nextFocusRight="@id/main_search" android:nextFocusRight="@id/main_search"
android:nextFocusUp="@id/nav_rail_view" android:nextFocusUp="@id/nav_rail_view"

View file

@ -441,7 +441,12 @@
<string name="pref_category_actions">Actions</string> <string name="pref_category_actions">Actions</string>
<string name="pref_category_cache">Cache</string> <string name="pref_category_cache">Cache</string>
<string name="pref_category_android_tv">Android TV</string> <string name="pref_category_android_tv">Android TV</string>
<string name="pref_category_android_tv_key" translatable="false" >pref_category_android_tv_key</string>
<string name="pref_category_gestures">Gestures</string> <string name="pref_category_gestures">Gestures</string>
<string name="pref_category_gestures_key" translatable="false">pref_category_gestures_key</string>
<string name="pref_category_security">Security</string>
<string name="pref_category_security_key" translatable="false">pref_category_security_key</string>
<string name="pref_category_accounts">Accounts</string>
<string name="pref_category_player_features">Player features</string> <string name="pref_category_player_features">Player features</string>
<string name="pref_category_subtitles">Subtitles</string> <string name="pref_category_subtitles">Subtitles</string>
<string name="pref_category_player_layout">Layout</string> <string name="pref_category_player_layout">Layout</string>
@ -474,6 +479,7 @@
<string name="simkl_key" translatable="false">simkl_key</string> <string name="simkl_key" translatable="false">simkl_key</string>
<string name="mal_key" translatable="false">mal_key</string> <string name="mal_key" translatable="false">mal_key</string>
<string name="opensubtitles_key" translatable="false">opensubtitles_key</string> <string name="opensubtitles_key" translatable="false">opensubtitles_key</string>
<string name="subdl_key" translatable="false">subdl_key</string>
<string name="nginx_key" translatable="false">nginx_key</string> <string name="nginx_key" translatable="false">nginx_key</string>
<string name="example_password">password123</string> <string name="example_password">password123</string>
<string name="example_username">Username</string> <string name="example_username">Username</string>
@ -775,4 +781,5 @@
<string name="biometric_prompt_description">After a few failed attempts, the prompt will close. Simply restart the app to try again.</string> <string name="biometric_prompt_description">After a few failed attempts, the prompt will close. Simply restart the app to try again.</string>
<string name="biometric_warning">Your CloudStream data has been backed up now. Although the possibility of this is very low, all devices can behave differently. In the rare case, that you get locked out from accessing the app, clear the app data completely and restore from a backup. We are very sorry for any inconvenience arising from this.</string> <string name="biometric_warning">Your CloudStream data has been backed up now. Although the possibility of this is very low, all devices can behave differently. In the rare case, that you get locked out from accessing the app, clear the app data completely and restore from a backup. We are very sorry for any inconvenience arising from this.</string>
<string name="reset_btn">Reset</string> <string name="reset_btn">Reset</string>
<string name="cs3wiki">CloudStream Wiki</string>
</resources> </resources>

View file

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:title="@string/pref_category_accounts">
<Preference <Preference
android:icon="@drawable/mal_logo" android:icon="@drawable/mal_logo"
@ -17,12 +21,22 @@
android:icon="@drawable/open_subtitles_icon" android:icon="@drawable/open_subtitles_icon"
android:key="@string/opensubtitles_key" /> android:key="@string/opensubtitles_key" />
<Preference
android:icon="@drawable/subdl_logo_big"
android:key="@string/subdl_key" />
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:icon="@drawable/ic_outline_account_circle_24" android:icon="@drawable/ic_outline_account_circle_24"
android:key="@string/skip_startup_account_select_key" android:key="@string/skip_startup_account_select_key"
android:title="@string/skip_startup_account_select_pref" /> android:title="@string/skip_startup_account_select_pref" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/pref_category_security"
app:key="@string/pref_category_security_key">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="@string/biometric_key" android:key="@string/biometric_key"
android:defaultValue="false" android:defaultValue="false"
@ -30,4 +44,6 @@
android:icon="@drawable/ic_fingerprint" android:icon="@drawable/ic_fingerprint"
android:title="@string/biometric_setting" /> android:title="@string/biometric_setting" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -86,6 +86,14 @@
android:action="android.intent.action.VIEW" android:action="android.intent.action.VIEW"
android:data="https://discord.gg/5Hus6fM" /> android:data="https://discord.gg/5Hus6fM" />
</Preference> </Preference>
<Preference
android:title="@string/cs3wiki"
android:icon="@drawable/baseline_description_24"
app:summary="https://cloudstream.miraheze.org/">
<intent
android:action="android.intent.action.VIEW"
android:data="https://cloudstream.miraheze.org/" />
</Preference>
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -101,7 +101,8 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/pref_category_gestures"> android:title="@string/pref_category_gestures"
app:key="@string/pref_category_gestures_key">
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_ondemand_video_24" android:icon="@drawable/ic_baseline_ondemand_video_24"
android:summary="@string/swipe_to_seek_settings_des" android:summary="@string/swipe_to_seek_settings_des"
@ -166,7 +167,8 @@
android:title="@string/video_buffer_clear_settings" /> android:title="@string/video_buffer_clear_settings" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/pref_category_android_tv"> android:title="@string/pref_category_android_tv"
android:key="@string/pref_category_android_tv_key" >
<SeekBarPreference <SeekBarPreference
android:defaultValue="10" android:defaultValue="10"
android:max="60" android:max="60"

View file

@ -1,13 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference
android:defaultValue="true"
android:icon="@drawable/ic_baseline_bug_report_24"
android:key="acra.disable"
android:summaryOff="@string/bug_report_settings_off"
android:summaryOn="@string/bug_report_settings_on"
android:title="@string/pref_disable_acra" />
<PreferenceCategory <PreferenceCategory
android:title="@string/pref_category_app_updates"> android:title="@string/pref_category_app_updates">
@ -80,5 +73,12 @@
android:icon="@drawable/ic_baseline_construction_24" android:icon="@drawable/ic_baseline_construction_24"
android:title="@string/redo_setup_process" android:title="@string/redo_setup_process"
app:key="@string/redo_setup_key" /> app:key="@string/redo_setup_key" />
<SwitchPreference
android:defaultValue="true"
android:icon="@drawable/ic_baseline_bug_report_24"
android:key="acra.disable"
android:summaryOff="@string/bug_report_settings_off"
android:summaryOn="@string/bug_report_settings_on"
android:title="@string/pref_disable_acra" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -6,7 +6,7 @@ buildscript {
dependencies { dependencies {
classpath("com.android.tools.build:gradle:8.2.2") classpath("com.android.tools.build:gradle:8.2.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10") classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
// Universal build config // Universal build config
classpath("com.codingfeline.buildkonfig:buildkonfig-gradle-plugin:0.15.1") classpath("com.codingfeline.buildkonfig:buildkonfig-gradle-plugin:0.15.1")