mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge remote-tracking branch 'origin/master' into dialog2
This commit is contained in:
commit
7f541a89aa
29 changed files with 403 additions and 382 deletions
|
@ -164,7 +164,7 @@ dependencies {
|
|||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
||||
// 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.navigation:navigation-ui-ktx:2.7.7")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
|
||||
|
@ -174,7 +174,7 @@ dependencies {
|
|||
// Design & UI
|
||||
implementation("jp.wasabeef:glide-transformations:4.3.0")
|
||||
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.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
|
||||
|
@ -185,7 +185,7 @@ dependencies {
|
|||
|
||||
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP
|
||||
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")
|
||||
|
||||
// Media 3 (ExoPlayer)
|
||||
|
@ -202,7 +202,7 @@ dependencies {
|
|||
// PlayBack
|
||||
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.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 */
|
||||
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
|
||||
|
||||
// Extensions & Other Libs
|
||||
implementation("org.mozilla:rhino:1.7.13") /* run JavaScript
|
||||
^ Don't Bump RhinoJS to 1.7.14,`NoClassDefFoundError` Occurs and Trailers won't play (even with Desugaring)
|
||||
NewPipeExtractor Issue */
|
||||
implementation("org.mozilla:rhino:1.7.15") // run JavaScript
|
||||
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("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9
|
||||
|
|
|
@ -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)
|
||||
if (response != null)
|
||||
return response
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,3 +21,8 @@ class FourPlayRu : ContentX() {
|
|||
override var name = "FourPlayRu"
|
||||
override var mainUrl = "https://four.playru.net"
|
||||
}
|
||||
|
||||
class FourPichive : ContentX() {
|
||||
override var name = "FourPichive"
|
||||
override var mainUrl = "https://four.pichive.online"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
|||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.amap
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import java.net.URLDecoder
|
||||
|
@ -26,6 +27,7 @@ class VidSrcTo : ExtractorApi() {
|
|||
val res = app.get("$mainUrl/ajax/embed/episode/$mediaId/sources").parsedSafe<VidsrctoEpisodeSources>() ?: return
|
||||
if (res.status != 200) return
|
||||
res.result?.amap { source ->
|
||||
try {
|
||||
val embedRes = app.get("$mainUrl/ajax/embed/source/${source.id}").parsedSafe<VidsrctoEmbedSource>() ?: return@amap
|
||||
val finalUrl = DecryptUrl(embedRes.result.encUrl)
|
||||
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)
|
||||
"Filemoon" -> FileMoon().getUrl(finalUrl, referer, subtitleCallback, callback)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -166,18 +166,10 @@ open class TraktProvider : MainAPI() {
|
|||
val resSeasons = getApi("$traktApiUrl/shows/${mediaDetails?.ids?.trakt.toString()}/seasons?extended=cloud9,full,episodes")
|
||||
val episodes = mutableListOf<Episode>()
|
||||
val seasons = parseJson<List<Seasons>>(resSeasons)
|
||||
val seasonsNames = mutableListOf<SeasonData>()
|
||||
var nextAir: NextAiring? = null
|
||||
|
||||
seasons.forEach { season ->
|
||||
|
||||
seasonsNames.add(
|
||||
SeasonData(
|
||||
season.number!!,
|
||||
season.title
|
||||
)
|
||||
)
|
||||
|
||||
season.episodes?.map { episode ->
|
||||
|
||||
val linkData = LinkData(
|
||||
|
@ -250,7 +242,6 @@ open class TraktProvider : MainAPI() {
|
|||
this.comingSoon = isUpcoming(mediaDetails.released)
|
||||
//posterHeaders
|
||||
this.nextAiring = nextAir
|
||||
this.seasonNames = seasonsNames
|
||||
this.backgroundPosterUrl = getOriginalWidthImageUrl(backDropUrl)
|
||||
this.contentRating = mediaDetails.certification
|
||||
addTrailer(mediaDetails.trailer)
|
||||
|
|
|
@ -12,9 +12,8 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
val aniListApi = AniListApi(0)
|
||||
val openSubtitlesApi = OpenSubtitlesApi(0)
|
||||
val simklApi = SimklApi(0)
|
||||
val indexSubtitlesApi = IndexSubtitleApi()
|
||||
val addic7ed = Addic7ed()
|
||||
val subDl = SubDL()
|
||||
val subDlApi = SubDlApi(0)
|
||||
val localListApi = LocalList()
|
||||
|
||||
// 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
|
||||
val accountManagers
|
||||
get() = listOf(
|
||||
malApi, aniListApi, openSubtitlesApi, simklApi //nginxApi
|
||||
malApi, aniListApi, openSubtitlesApi, subDlApi, simklApi //nginxApi
|
||||
)
|
||||
|
||||
// used for active syncing
|
||||
|
@ -36,14 +35,16 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
)
|
||||
|
||||
val inAppAuths
|
||||
get() = listOf(openSubtitlesApi)//, nginxApi)
|
||||
get() = listOf<InAppAuthAPIManager>(
|
||||
openSubtitlesApi,
|
||||
subDlApi
|
||||
)//, nginxApi)
|
||||
|
||||
val subtitleProviders
|
||||
get() = listOf(
|
||||
openSubtitlesApi,
|
||||
indexSubtitlesApi, // they got anti scraping measures in place :(
|
||||
addic7ed,
|
||||
subDl
|
||||
subDlApi
|
||||
)
|
||||
|
||||
const val appString = "cloudstreamapp"
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +1,80 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
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.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.SubtitleResource
|
||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI.LoginInfo
|
||||
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager
|
||||
|
||||
class SubDL : AbstractSubProvider {
|
||||
//API Documentation: https://subdl.com/api-doc
|
||||
val mainUrl = "https://subdl.com/"
|
||||
val name = "SubDL"
|
||||
class SubDlApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi {
|
||||
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 {
|
||||
const val APIKEY = "zRJl5QA-8jNA2i0pE8cxANbEukANp7IM"
|
||||
const val APIENDPOINT = "https://api.subdl.com/api/v1/subtitles"
|
||||
const val APIURL = "https://api.subdl.com"
|
||||
const val APIENDPOINT = "$APIURL/api/v1/subtitles"
|
||||
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>? {
|
||||
|
@ -37,8 +96,8 @@ class SubDL : AbstractSubProvider {
|
|||
|
||||
val searchQueryUrl = when (idQuery) {
|
||||
//Use imdb/tmdb id to search if its valid
|
||||
null -> "$APIENDPOINT?api_key=$APIKEY&film_name=$queryText&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
|
||||
else -> "$APIENDPOINT?api_key=$APIKEY$idQuery&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=${currentSession?.apiKey}$idQuery&languages=${query.lang}$epQuery$seasonQuery$yearQuery"
|
||||
}
|
||||
|
||||
val req = app.get(
|
||||
|
@ -49,7 +108,7 @@ class SubDL : AbstractSubProvider {
|
|||
)
|
||||
|
||||
return req.parsedSafe<ApiResponse>()?.subtitles?.map { subtitle ->
|
||||
val name = subtitle.releaseName
|
||||
|
||||
val lang = subtitle.lang.replaceFirstChar { it.uppercase() }
|
||||
val resEpNum = subtitle.episode ?: query.epNumber
|
||||
val resSeasonNum = subtitle.season ?: query.seasonNumber
|
||||
|
@ -57,13 +116,14 @@ class SubDL : AbstractSubProvider {
|
|||
|
||||
AbstractSubtitleEntities.SubtitleEntity(
|
||||
idPrefix = this.idPrefix,
|
||||
name = name,
|
||||
name = subtitle.releaseName,
|
||||
lang = lang,
|
||||
data = "${DOWNLOADENDPOINT}${subtitle.url}",
|
||||
type = type,
|
||||
source = this.name,
|
||||
epNumber = resEpNum,
|
||||
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(
|
||||
@JsonProperty("status") val status: Boolean? = null,
|
||||
@JsonProperty("results") val results: List<Result>? = null,
|
||||
|
@ -96,7 +238,10 @@ class SubDL : AbstractSubProvider {
|
|||
@JsonProperty("lang") val lang: String,
|
||||
@JsonProperty("author") val author: String? = null,
|
||||
@JsonProperty("url") val url: String? = null,
|
||||
@JsonProperty("subtitlePage") val subtitlePage: String? = null,
|
||||
@JsonProperty("season") val season: Int? = null,
|
||||
@JsonProperty("episode") val episode: Int? = null,
|
||||
@JsonProperty("language") val language: String? = null,
|
||||
@JsonProperty("hi") val hearingImpaired: Boolean? = null,
|
||||
)
|
||||
}
|
|
@ -17,7 +17,7 @@ import com.lagradost.safefile.SafeFile
|
|||
const val DTAG = "PlayerActivity"
|
||||
|
||||
class DownloadedPlayerActivity : AppCompatActivity() {
|
||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
CommonActivity.dispatchKeyEvent(this, event)?.let {
|
||||
return it
|
||||
}
|
||||
|
|
|
@ -1728,7 +1728,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
txt(R.string.episode_action_cast_mirror)
|
||||
) { (result, index) ->
|
||||
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 ->
|
||||
session.sendMessage(
|
||||
|
|
|
@ -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.openSubtitlesApi
|
||||
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.InAppAuthAPI
|
||||
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.isLayout
|
||||
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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
|
@ -297,10 +299,10 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback {
|
|||
hideKeyboard()
|
||||
setPreferencesFromResource(R.xml.settings_account, rootKey)
|
||||
|
||||
// hide preference on tvs and emulators
|
||||
getPref(R.string.biometric_key)?.isEnabled = isLayout(PHONE)
|
||||
//Hides the security category on TV as it's only Biometric for now
|
||||
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
|
||||
|
||||
if (deviceHasPasswordPinLock(ctx)) {
|
||||
|
@ -324,12 +326,12 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback {
|
|||
R.string.anilist_key to aniListApi,
|
||||
R.string.simkl_key to simklApi,
|
||||
R.string.opensubtitles_key to openSubtitlesApi,
|
||||
R.string.subdl_key to subDlApi,
|
||||
)
|
||||
|
||||
for ((key, api) in syncApis) {
|
||||
getPref(key)?.apply {
|
||||
title =
|
||||
getString(R.string.login_format).format(api.name, getString(R.string.account))
|
||||
title = api.name
|
||||
setOnPreferenceClickListener {
|
||||
val info = api.loginInfo()
|
||||
if (info != null) {
|
||||
|
|
|
@ -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.TV
|
||||
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.UIHelper
|
||||
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.
|
||||
* */
|
||||
|
|
|
@ -27,10 +27,13 @@ import com.lagradost.cloudstream3.mvvm.logError
|
|||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.network.initClient
|
||||
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.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.beneneCount
|
||||
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.hideOn
|
||||
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.setUpToolbar
|
||||
|
@ -208,9 +211,7 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
// disable preference on tvs and emulators
|
||||
getPref(R.string.battery_optimisation_key)?.isEnabled = isLayout(PHONE)
|
||||
getPref(R.string.battery_optimisation_key)?.setOnPreferenceClickListener {
|
||||
getPref(R.string.battery_optimisation_key)?.hideOn(TV or EMULATOR)?.setOnPreferenceClickListener {
|
||||
val ctx = context ?: return@setOnPreferenceClickListener false
|
||||
|
||||
if (isAppRestricted(ctx)) {
|
||||
|
|
|
@ -7,8 +7,14 @@ import androidx.preference.PreferenceFragmentCompat
|
|||
import androidx.preference.PreferenceManager
|
||||
import com.lagradost.cloudstream3.R
|
||||
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.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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
|
@ -31,6 +37,18 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
setPreferencesFromResource(R.xml.settings_player, rootKey)
|
||||
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 {
|
||||
val prefNames = resources.getStringArray(R.array.video_buffer_length_names)
|
||||
val prefValues = resources.getIntArray(R.array.video_buffer_length_values)
|
||||
|
@ -227,6 +245,5 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider
|
||||
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.amap
|
||||
|
@ -181,8 +182,11 @@ class PluginsViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
private suspend fun updatePluginListPrivate(context: Context, repositoryUrl: String) {
|
||||
val isAdult = settingsForProvider.enableAdult
|
||||
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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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_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.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
|
@ -64,6 +65,7 @@ object BackupUtils {
|
|||
PLUGINS_KEY_LOCAL,
|
||||
|
||||
OPEN_SUBTITLES_USER_KEY,
|
||||
SUBDL_SUBTITLES_USER_KEY,
|
||||
|
||||
DOWNLOAD_EPISODE_CACHE,
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ import com.lagradost.cloudstream3.extractors.Gdriveplayerorg
|
|||
import com.lagradost.cloudstream3.extractors.Gdriveplayerus
|
||||
import com.lagradost.cloudstream3.extractors.Gofile
|
||||
import com.lagradost.cloudstream3.extractors.GuardareStream
|
||||
import com.lagradost.cloudstream3.extractors.GoodstreamExtractor
|
||||
import com.lagradost.cloudstream3.extractors.Guccihide
|
||||
import com.lagradost.cloudstream3.extractors.Hxfile
|
||||
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.PlayRu
|
||||
import com.lagradost.cloudstream3.extractors.FourPlayRu
|
||||
import com.lagradost.cloudstream3.extractors.FourPichive
|
||||
import com.lagradost.cloudstream3.extractors.HDMomPlayer
|
||||
import com.lagradost.cloudstream3.extractors.HDPlayerSystem
|
||||
import com.lagradost.cloudstream3.extractors.VideoSeyred
|
||||
|
@ -748,6 +750,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
FourCX(),
|
||||
PlayRu(),
|
||||
FourPlayRu(),
|
||||
FourPichive(),
|
||||
HDMomPlayer(),
|
||||
HDPlayerSystem(),
|
||||
VideoSeyred(),
|
||||
|
@ -877,6 +880,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
|||
Gdriveplayerorg(),
|
||||
Gdriveplayerus(),
|
||||
Gdriveplayerco(),
|
||||
GoodstreamExtractor(),
|
||||
Gdriveplayer(),
|
||||
DatabaseGdrive(),
|
||||
DatabaseGdrive2(),
|
||||
|
|
10
app/src/main/res/drawable/subdl_logo_big.xml
Normal file
10
app/src/main/res/drawable/subdl_logo_big.xml
Normal 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>
|
|
@ -64,12 +64,14 @@
|
|||
<TextView
|
||||
android:id="@+id/account_switch_account"
|
||||
android:text="@string/switch_account"
|
||||
style="@style/SettingsItem" />
|
||||
style="@style/SettingsItem"
|
||||
android:focusable="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_logout"
|
||||
android:text="@string/logout"
|
||||
style="@style/SettingsItem">
|
||||
style="@style/SettingsItem"
|
||||
android:focusable="true">
|
||||
|
||||
<requestFocus />
|
||||
</TextView>
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent">
|
||||
android:layout_width="match_parent"
|
||||
android:focusable="true">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/account_profile_picture_holder"
|
||||
|
|
|
@ -13,12 +13,14 @@
|
|||
tools:listitem="@layout/account_single"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_add"
|
||||
android:text="@string/add_account"
|
||||
style="@style/SettingsItem">
|
||||
style="@style/SettingsItem"
|
||||
android:focusable="true">
|
||||
|
||||
<requestFocus />
|
||||
</TextView>
|
||||
|
|
|
@ -107,6 +107,7 @@
|
|||
android:layout_margin="10dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/change_providers_img_des"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@id/year_btt"
|
||||
android:nextFocusRight="@id/main_search"
|
||||
android:nextFocusUp="@id/nav_rail_view"
|
||||
|
|
|
@ -441,7 +441,12 @@
|
|||
<string name="pref_category_actions">Actions</string>
|
||||
<string name="pref_category_cache">Cache</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_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_subtitles">Subtitles</string>
|
||||
<string name="pref_category_player_layout">Layout</string>
|
||||
|
@ -474,6 +479,7 @@
|
|||
<string name="simkl_key" translatable="false">simkl_key</string>
|
||||
<string name="mal_key" translatable="false">mal_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="example_password">password123</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_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="cs3wiki">CloudStream Wiki</string>
|
||||
</resources>
|
|
@ -1,5 +1,9 @@
|
|||
<?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
|
||||
android:icon="@drawable/mal_logo"
|
||||
|
@ -17,12 +21,22 @@
|
|||
android:icon="@drawable/open_subtitles_icon"
|
||||
android:key="@string/opensubtitles_key" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/subdl_logo_big"
|
||||
android:key="@string/subdl_key" />
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_outline_account_circle_24"
|
||||
android:key="@string/skip_startup_account_select_key"
|
||||
android:title="@string/skip_startup_account_select_pref" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_category_security"
|
||||
app:key="@string/pref_category_security_key">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/biometric_key"
|
||||
android:defaultValue="false"
|
||||
|
@ -30,4 +44,6 @@
|
|||
android:icon="@drawable/ic_fingerprint"
|
||||
android:title="@string/biometric_setting" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
|
@ -86,6 +86,14 @@
|
|||
android:action="android.intent.action.VIEW"
|
||||
android:data="https://discord.gg/5Hus6fM" />
|
||||
</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>
|
||||
</PreferenceScreen>
|
|
@ -101,7 +101,8 @@
|
|||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_category_gestures">
|
||||
android:title="@string/pref_category_gestures"
|
||||
app:key="@string/pref_category_gestures_key">
|
||||
<SwitchPreference
|
||||
android:icon="@drawable/ic_baseline_ondemand_video_24"
|
||||
android:summary="@string/swipe_to_seek_settings_des"
|
||||
|
@ -166,7 +167,8 @@
|
|||
android:title="@string/video_buffer_clear_settings" />
|
||||
</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
|
||||
android:defaultValue="10"
|
||||
android:max="60"
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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
|
||||
android:title="@string/pref_category_app_updates">
|
||||
|
@ -80,5 +73,12 @@
|
|||
android:icon="@drawable/ic_baseline_construction_24"
|
||||
android:title="@string/redo_setup_process"
|
||||
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>
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -6,7 +6,7 @@ buildscript {
|
|||
|
||||
dependencies {
|
||||
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")
|
||||
// Universal build config
|
||||
classpath("com.codingfeline.buildkonfig:buildkonfig-gradle-plugin:0.15.1")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue