mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Compare commits
12 commits
a936fc9ac8
...
ce3359c0fa
Author | SHA1 | Date | |
---|---|---|---|
|
ce3359c0fa | ||
|
30839e3a09 | ||
|
c363596d4f | ||
|
dc8ba3399c | ||
|
e6b9d621f9 | ||
|
0019f85501 | ||
|
0744189020 | ||
|
4399a612df | ||
|
e01ff4d843 | ||
|
6cef9f7ea2 | ||
|
9a18ef6411 | ||
|
6df3ef14f6 |
24 changed files with 707 additions and 80 deletions
|
@ -4,17 +4,16 @@
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="delegatedBuild" value="true" />
|
|
||||||
<option name="testRunner" value="GRADLE" />
|
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="jbr-17" />
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
<option value="$PROJECT_DIR$/library" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="resolveExternalAnnotations" value="false" />
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
||||||
import org.jetbrains.dokka.gradle.DokkaTask
|
import org.jetbrains.dokka.gradle.DokkaTask
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
@ -13,6 +14,7 @@ plugins {
|
||||||
|
|
||||||
val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
|
val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
|
||||||
val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first()
|
val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first()
|
||||||
|
var isLibraryDebug = false
|
||||||
|
|
||||||
fun String.execute() = ByteArrayOutputStream().use { baot ->
|
fun String.execute() = ByteArrayOutputStream().use { baot ->
|
||||||
if (project.exec {
|
if (project.exec {
|
||||||
|
@ -103,6 +105,7 @@ android {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
|
isLibraryDebug = true
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
|
@ -199,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:6dc25f7") /* For Trailers
|
implementation("com.github.TeamNewPipe.NewPipeExtractor:NewPipeExtractor:6dc25f7b97") /* 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
|
||||||
|
|
||||||
|
@ -232,18 +235,37 @@ dependencies {
|
||||||
implementation("androidx.work:work-runtime:2.9.0")
|
implementation("androidx.work:work-runtime:2.9.0")
|
||||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||||
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
|
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
|
||||||
|
|
||||||
|
implementation(project(":library") {
|
||||||
|
this.extra.set("isDebug", isLibraryDebug)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("androidSourcesJar", Jar::class) {
|
tasks.register<Jar>("androidSourcesJar") {
|
||||||
archiveClassifier.set("sources")
|
archiveClassifier.set("sources")
|
||||||
from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources
|
from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources
|
||||||
}
|
}
|
||||||
|
|
||||||
// For GradLew Plugin
|
tasks.register<Copy>("copyJar") {
|
||||||
tasks.register("makeJar", Copy::class) {
|
from(
|
||||||
from("build/intermediates/compile_app_classes_jar/prereleaseDebug")
|
"build/intermediates/compile_app_classes_jar/prereleaseDebug",
|
||||||
into("build")
|
"../library/build/libs"
|
||||||
include("classes.jar")
|
)
|
||||||
|
into("build/app-classes")
|
||||||
|
include("classes.jar", "library-jvm*.jar")
|
||||||
|
// Remove the version
|
||||||
|
rename("library-jvm.*.jar", "library-jvm.jar")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the app classes and the library classes into classes.jar
|
||||||
|
tasks.register<Jar>("makeJar") {
|
||||||
|
dependsOn(tasks.getByName("copyJar"))
|
||||||
|
from(
|
||||||
|
zipTree("build/app-classes/classes.jar"),
|
||||||
|
zipTree("build/app-classes/library-jvm.jar")
|
||||||
|
)
|
||||||
|
destinationDirectory.set(layout.buildDirectory)
|
||||||
|
archivesName = "classes"
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
|
|
|
@ -743,8 +743,6 @@ fun base64Encode(array: ByteArray): String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorLoadingException(message: String? = null) : Exception(message)
|
|
||||||
|
|
||||||
fun MainAPI.fixUrlNull(url: String?): String? {
|
fun MainAPI.fixUrlNull(url: String?): String? {
|
||||||
if (url.isNullOrEmpty()) {
|
if (url.isNullOrEmpty()) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -22,17 +22,15 @@ class VidSrcTo : ExtractorApi() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
) {
|
) {
|
||||||
app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id")?.let { mediaId ->
|
val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") ?: return
|
||||||
val res = app.get("$mainUrl/ajax/embed/episode/$mediaId/sources").parsedSafe<VidsrctoEpisodeSources>()
|
val res = app.get("$mainUrl/ajax/embed/episode/$mediaId/sources").parsedSafe<VidsrctoEpisodeSources>() ?: return
|
||||||
if (res?.status == 200) {
|
if (res?.status != 200) return
|
||||||
res.result?.amap { source ->
|
res.result?.amap { source ->
|
||||||
val embedRes = app.get("$mainUrl/ajax/embed/source/${source.id}").parsedSafe<VidsrctoEmbedSource>()
|
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)
|
||||||
when (source.title) {
|
when (source.title) {
|
||||||
"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)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +44,7 @@ class VidSrcTo : ExtractorApi() {
|
||||||
data = cipher.doFinal(data)
|
data = cipher.doFinal(data)
|
||||||
return URLDecoder.decode(data.toString(Charsets.UTF_8), "utf-8")
|
return URLDecoder.decode(data.toString(Charsets.UTF_8), "utf-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class VidsrctoEpisodeSources(
|
data class VidsrctoEpisodeSources(
|
||||||
@JsonProperty("status") val status: Int,
|
@JsonProperty("status") val status: Int,
|
||||||
@JsonProperty("result") val result: List<VidsrctoResult>?
|
@JsonProperty("result") val result: List<VidsrctoResult>?
|
||||||
|
|
|
@ -25,9 +25,13 @@ open class Vidmoly : ExtractorApi() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val headers = mapOf(
|
||||||
|
"User-Agent" to "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36",
|
||||||
|
"Sec-Fetch-Dest" to "iframe"
|
||||||
|
)
|
||||||
val script = app.get(
|
val script = app.get(
|
||||||
url,
|
url,
|
||||||
|
headers = headers,
|
||||||
referer = referer,
|
referer = referer,
|
||||||
).document.select("script")
|
).document.select("script")
|
||||||
.find { it.data().contains("sources:") }?.data()
|
.find { it.data().contains("sources:") }?.data()
|
||||||
|
@ -66,4 +70,4 @@ open class Vidmoly : ExtractorApi() {
|
||||||
@JsonProperty("kind") val kind: String? = null,
|
@JsonProperty("kind") val kind: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ open class TmdbProvider : MainAPI() {
|
||||||
this.id,
|
this.id,
|
||||||
episode.episode_number,
|
episode.episode_number,
|
||||||
episode.season_number,
|
episode.season_number,
|
||||||
|
this.name ?: this.original_name,
|
||||||
).toJson(),
|
).toJson(),
|
||||||
episode.name,
|
episode.name,
|
||||||
episode.season_number,
|
episode.season_number,
|
||||||
|
@ -122,6 +123,7 @@ open class TmdbProvider : MainAPI() {
|
||||||
this.id,
|
this.id,
|
||||||
episodeNum,
|
episodeNum,
|
||||||
season.season_number,
|
season.season_number,
|
||||||
|
this.name ?: this.original_name,
|
||||||
).toJson(),
|
).toJson(),
|
||||||
season = season.season_number
|
season = season.season_number
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,430 @@
|
||||||
|
package com.lagradost.cloudstream3.metaproviders
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAlias
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
import java.util.Locale
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
open class TraktProvider : MainAPI() {
|
||||||
|
override var name = "Trakt"
|
||||||
|
override val hasMainPage = true
|
||||||
|
override val providerType = ProviderType.MetaProvider
|
||||||
|
override val supportedTypes = setOf(
|
||||||
|
TvType.Movie,
|
||||||
|
TvType.TvSeries,
|
||||||
|
TvType.Anime,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val traktClientId = base64Decode("N2YzODYwYWQzNGI4ZTZmOTdmN2I5MTA0ZWQzMzEwOGI0MmQ3MTdlMTM0MmM2NGMxMTg5NGE1MjUyYTQ3NjE3Zg==")
|
||||||
|
private val traktApiUrl = base64Decode("aHR0cHM6Ly9hcGl6LnRyYWt0LnR2")
|
||||||
|
|
||||||
|
override val mainPage = mainPageOf(
|
||||||
|
"$traktApiUrl/movies/trending" to "Trending Movies", //Most watched movies right now
|
||||||
|
"$traktApiUrl/movies/popular" to "Popular Movies", //The most popular movies for all time
|
||||||
|
"$traktApiUrl/shows/trending" to "Trending Shows", //Most watched Shows right now
|
||||||
|
"$traktApiUrl/shows/popular" to "Popular Shows", //The most popular Shows for all time
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||||
|
|
||||||
|
val apiResponse = getApi("${request.data}?extended=cloud9,full&page=$page")
|
||||||
|
|
||||||
|
val results = parseJson<List<MediaDetails>>(apiResponse).map { element ->
|
||||||
|
element.toSearchResponse()
|
||||||
|
}
|
||||||
|
return newHomePageResponse(request.name, results)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MediaDetails.toSearchResponse(): SearchResponse {
|
||||||
|
|
||||||
|
val media = this.media ?: this
|
||||||
|
val mediaType = if (media.ids?.tvdb == null) TvType.Movie else TvType.TvSeries
|
||||||
|
val poster = media.images?.poster?.firstOrNull()
|
||||||
|
|
||||||
|
if (mediaType == TvType.Movie) {
|
||||||
|
return newMovieSearchResponse(
|
||||||
|
name = media.title!!,
|
||||||
|
url = Data(
|
||||||
|
type = mediaType,
|
||||||
|
mediaDetails = media,
|
||||||
|
).toJson(),
|
||||||
|
type = TvType.Movie,
|
||||||
|
) {
|
||||||
|
posterUrl = fixPath(poster)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return newTvSeriesSearchResponse(
|
||||||
|
name = media.title!!,
|
||||||
|
url = Data(
|
||||||
|
type = mediaType,
|
||||||
|
mediaDetails = media,
|
||||||
|
).toJson(),
|
||||||
|
type = TvType.TvSeries,
|
||||||
|
) {
|
||||||
|
this.posterUrl = fixPath(poster)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse>? {
|
||||||
|
val apiResponse = getApi("$traktApiUrl/search/movie,show?extended=cloud9,full&limit=20&page=1&query=$query")
|
||||||
|
|
||||||
|
val results = parseJson<List<MediaDetails>>(apiResponse).map { element ->
|
||||||
|
element.toSearchResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
override suspend fun load(url: String): LoadResponse {
|
||||||
|
|
||||||
|
val data = parseJson<Data>(url)
|
||||||
|
val mediaDetails = data.mediaDetails
|
||||||
|
val moviesOrShows = if (data.type == TvType.Movie) "movies" else "shows"
|
||||||
|
|
||||||
|
val posterUrl = mediaDetails?.images?.poster?.firstOrNull()
|
||||||
|
val backDropUrl = mediaDetails?.images?.fanart?.firstOrNull()
|
||||||
|
|
||||||
|
val resActor = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/people?extended=cloud9,full")
|
||||||
|
|
||||||
|
val actors = parseJson<People>(resActor).cast?.map {
|
||||||
|
ActorData(
|
||||||
|
Actor(
|
||||||
|
name = it.person?.name!!,
|
||||||
|
image = getWidthImageUrl(it.person.images?.headshot?.firstOrNull(), "w500")
|
||||||
|
),
|
||||||
|
roleString = it.character
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resRelated = getApi("$traktApiUrl/$moviesOrShows/${mediaDetails?.ids?.trakt}/related?extended=cloud9,full&limit=20")
|
||||||
|
|
||||||
|
val relatedMedia = parseJson<List<MediaDetails>>(resRelated).map { it.toSearchResponse() }
|
||||||
|
|
||||||
|
val isCartoon = mediaDetails?.genres?.contains("animation") == true || mediaDetails?.genres?.contains("anime") == true
|
||||||
|
val isAnime = isCartoon && (mediaDetails?.language == "zh" || mediaDetails?.language == "ja")
|
||||||
|
val isAsian = !isAnime && (mediaDetails?.language == "zh" || mediaDetails?.language == "ko")
|
||||||
|
val isBollywood = mediaDetails?.country == "in"
|
||||||
|
|
||||||
|
if (data.type == TvType.Movie) {
|
||||||
|
|
||||||
|
val linkData = LinkData(
|
||||||
|
id = mediaDetails?.ids?.tmdb,
|
||||||
|
imdbId = mediaDetails?.ids?.imdb.toString(),
|
||||||
|
tvdbId = mediaDetails?.ids?.tvdb,
|
||||||
|
type = data.type.toString(),
|
||||||
|
title = mediaDetails?.title,
|
||||||
|
year = mediaDetails?.year,
|
||||||
|
orgTitle = mediaDetails?.title,
|
||||||
|
isAnime = isAnime,
|
||||||
|
//jpTitle = later if needed as it requires another network request,
|
||||||
|
airedDate = mediaDetails?.released
|
||||||
|
?: mediaDetails?.firstAired,
|
||||||
|
isAsian = isAsian,
|
||||||
|
isBollywood = isBollywood,
|
||||||
|
).toJson()
|
||||||
|
|
||||||
|
return newMovieLoadResponse(
|
||||||
|
name = mediaDetails?.title!!,
|
||||||
|
url = data.toJson(),
|
||||||
|
dataUrl = linkData.toJson(),
|
||||||
|
type = if (isAnime) TvType.AnimeMovie else TvType.Movie,
|
||||||
|
) {
|
||||||
|
this.name = mediaDetails.title
|
||||||
|
this.apiName = "Trakt"
|
||||||
|
this.type = if (isAnime) TvType.AnimeMovie else TvType.Movie
|
||||||
|
this.posterUrl = getOriginalWidthImageUrl(posterUrl)
|
||||||
|
this.year = mediaDetails.year
|
||||||
|
this.plot = mediaDetails.overview
|
||||||
|
this.rating = mediaDetails.rating?.times(1000)?.roundToInt()
|
||||||
|
this.tags = mediaDetails.genres
|
||||||
|
this.duration = mediaDetails.runtime
|
||||||
|
this.recommendations = relatedMedia
|
||||||
|
this.actors = actors
|
||||||
|
this.comingSoon = isUpcoming(mediaDetails.released)
|
||||||
|
//posterHeaders
|
||||||
|
this.backgroundPosterUrl = getOriginalWidthImageUrl(backDropUrl)
|
||||||
|
this.contentRating = mediaDetails.certification
|
||||||
|
addTrailer(mediaDetails.trailer)
|
||||||
|
addImdbId(mediaDetails.ids?.imdb)
|
||||||
|
addTMDbId(mediaDetails.ids?.tmdb.toString())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
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>()
|
||||||
|
|
||||||
|
seasons.forEach { season ->
|
||||||
|
|
||||||
|
seasonsNames.add(
|
||||||
|
SeasonData(
|
||||||
|
season.number!!,
|
||||||
|
season.title
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
season.episodes?.map { episode ->
|
||||||
|
|
||||||
|
val linkData = LinkData(
|
||||||
|
id = mediaDetails?.ids?.tmdb,
|
||||||
|
imdbId = mediaDetails?.ids?.imdb.toString(),
|
||||||
|
tvdbId = mediaDetails?.ids?.tvdb,
|
||||||
|
type = data.type.toString(),
|
||||||
|
season = episode.season,
|
||||||
|
episode = episode.number,
|
||||||
|
title = mediaDetails?.title,
|
||||||
|
year = mediaDetails?.year,
|
||||||
|
orgTitle = mediaDetails?.title,
|
||||||
|
isAnime = isAnime,
|
||||||
|
airedYear = mediaDetails?.year,
|
||||||
|
lastSeason = seasons.size,
|
||||||
|
epsTitle = episode.title,
|
||||||
|
//jpTitle = later if needed as it requires another network request,
|
||||||
|
date = episode.firstAired,
|
||||||
|
airedDate = episode.firstAired,
|
||||||
|
isAsian = isAsian,
|
||||||
|
isBollywood = isBollywood,
|
||||||
|
isCartoon = isCartoon
|
||||||
|
).toJson()
|
||||||
|
|
||||||
|
episodes.add(
|
||||||
|
Episode(
|
||||||
|
data = linkData.toJson(),
|
||||||
|
name = episode.title,
|
||||||
|
season = episode.season,
|
||||||
|
episode = episode.number,
|
||||||
|
posterUrl = fixPath(episode.images?.screenshot?.firstOrNull()),
|
||||||
|
rating = episode.rating?.times(10)?.roundToInt(),
|
||||||
|
description = episode.overview,
|
||||||
|
).apply {
|
||||||
|
this.addDate(episode.firstAired)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTvSeriesLoadResponse(
|
||||||
|
name = mediaDetails?.title!!,
|
||||||
|
url = data.toJson(),
|
||||||
|
type = if (isAnime) TvType.Anime else TvType.TvSeries,
|
||||||
|
episodes = episodes
|
||||||
|
) {
|
||||||
|
this.name = mediaDetails.title
|
||||||
|
this.apiName = "Trakt"
|
||||||
|
this.type = if (isAnime) TvType.Anime else TvType.TvSeries
|
||||||
|
this.episodes = episodes
|
||||||
|
this.posterUrl = getOriginalWidthImageUrl(posterUrl)
|
||||||
|
this.year = mediaDetails.year
|
||||||
|
this.plot = mediaDetails.overview
|
||||||
|
this.showStatus = getStatus(mediaDetails.status)
|
||||||
|
this.rating = mediaDetails.rating?.times(1000)?.roundToInt()
|
||||||
|
this.tags = mediaDetails.genres
|
||||||
|
this.duration = mediaDetails.runtime
|
||||||
|
this.recommendations = relatedMedia
|
||||||
|
this.actors = actors
|
||||||
|
this.comingSoon = isUpcoming(mediaDetails.released)
|
||||||
|
//posterHeaders
|
||||||
|
this.seasonNames = seasonsNames
|
||||||
|
this.backgroundPosterUrl = getOriginalWidthImageUrl(backDropUrl)
|
||||||
|
this.contentRating = mediaDetails.certification
|
||||||
|
addTrailer(mediaDetails.trailer)
|
||||||
|
addImdbId(mediaDetails.ids?.imdb)
|
||||||
|
addTMDbId(mediaDetails.ids?.tmdb.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getApi(url: String) : String {
|
||||||
|
return app.get(
|
||||||
|
url = url,
|
||||||
|
headers = mapOf(
|
||||||
|
"Content-Type" to "application/json",
|
||||||
|
"trakt-api-version" to "2",
|
||||||
|
"trakt-api-key" to traktClientId,
|
||||||
|
)
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isUpcoming(dateString: String?): Boolean {
|
||||||
|
return try {
|
||||||
|
val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||||
|
val dateTime = dateString?.let { format.parse(it)?.time } ?: return false
|
||||||
|
APIHolder.unixTimeMS < dateTime
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
logError(t)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStatus(t: String?): ShowStatus {
|
||||||
|
return when (t) {
|
||||||
|
"returning series" -> ShowStatus.Ongoing
|
||||||
|
"continuing" -> ShowStatus.Ongoing
|
||||||
|
else -> ShowStatus.Completed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fixPath(url: String?): String? {
|
||||||
|
url ?: return null
|
||||||
|
return "https://$url"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getWidthImageUrl(path: String?, width: String) : String? {
|
||||||
|
if (path == null) return null
|
||||||
|
if (!path.contains("image.tmdb.org")) return fixPath(path)
|
||||||
|
val fileName = Uri.parse(path).lastPathSegment ?: return null
|
||||||
|
return "https://image.tmdb.org/t/p/${width}/${fileName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOriginalWidthImageUrl(path: String?) : String? {
|
||||||
|
if (path == null) return null
|
||||||
|
if (!path.contains("image.tmdb.org")) return fixPath(path)
|
||||||
|
return getWidthImageUrl(path, "original")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Data(
|
||||||
|
val type: TvType? = null,
|
||||||
|
val mediaDetails: MediaDetails? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MediaDetails(
|
||||||
|
@JsonProperty("title") val title: String? = null,
|
||||||
|
@JsonProperty("year") val year: Int? = null,
|
||||||
|
@JsonProperty("ids") val ids: Ids? = null,
|
||||||
|
@JsonProperty("tagline") val tagline: String? = null,
|
||||||
|
@JsonProperty("overview") val overview: String? = null,
|
||||||
|
@JsonProperty("released") val released: String? = null,
|
||||||
|
@JsonProperty("runtime") val runtime: Int? = null,
|
||||||
|
@JsonProperty("country") val country: String? = null,
|
||||||
|
@JsonProperty("updatedAt") val updatedAt: String? = null,
|
||||||
|
@JsonProperty("trailer") val trailer: String? = null,
|
||||||
|
@JsonProperty("homepage") val homepage: String? = null,
|
||||||
|
@JsonProperty("status") val status: String? = null,
|
||||||
|
@JsonProperty("rating") val rating: Double? = null,
|
||||||
|
@JsonProperty("votes") val votes: Long? = null,
|
||||||
|
@JsonProperty("comment_count") val commentCount: Long? = null,
|
||||||
|
@JsonProperty("language") val language: String? = null,
|
||||||
|
@JsonProperty("languages") val languages: List<String>? = null,
|
||||||
|
@JsonProperty("available_translations") val availableTranslations: List<String>? = null,
|
||||||
|
@JsonProperty("genres") val genres: List<String>? = null,
|
||||||
|
@JsonProperty("certification") val certification: String? = null,
|
||||||
|
@JsonProperty("aired_episodes") val airedEpisodes: Int? = null,
|
||||||
|
@JsonProperty("first_aired") val firstAired: String? = null,
|
||||||
|
@JsonProperty("airs") val airs: Airs? = null,
|
||||||
|
@JsonProperty("network") val network: String? = null,
|
||||||
|
@JsonProperty("images") val images: Images? = null,
|
||||||
|
@JsonProperty("movie") @JsonAlias("show") val media: MediaDetails? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Airs(
|
||||||
|
@JsonProperty("day") val day: String? = null,
|
||||||
|
@JsonProperty("time") val time: String? = null,
|
||||||
|
@JsonProperty("timezone") val timezone: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Ids(
|
||||||
|
@JsonProperty("trakt") val trakt: Int? = null,
|
||||||
|
@JsonProperty("slug") val slug: String? = null,
|
||||||
|
@JsonProperty("tvdb") val tvdb: Int? = null,
|
||||||
|
@JsonProperty("imdb") val imdb: String? = null,
|
||||||
|
@JsonProperty("tmdb") val tmdb: Int? = null,
|
||||||
|
@JsonProperty("tvrage") val tvrage: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Images(
|
||||||
|
@JsonProperty("fanart") val fanart: List<String>? = null,
|
||||||
|
@JsonProperty("poster") val poster: List<String>? = null,
|
||||||
|
@JsonProperty("logo") val logo: List<String>? = null,
|
||||||
|
@JsonProperty("clearart") val clearart: List<String>? = null,
|
||||||
|
@JsonProperty("banner") val banner: List<String>? = null,
|
||||||
|
@JsonProperty("thumb") val thumb: List<String>? = null,
|
||||||
|
@JsonProperty("screenshot") val screenshot: List<String>? = null,
|
||||||
|
@JsonProperty("headshot") val headshot: List<String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class People(
|
||||||
|
@JsonProperty("cast") val cast: List<Cast>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Cast(
|
||||||
|
@JsonProperty("character") val character: String? = null,
|
||||||
|
@JsonProperty("characters") val characters: List<String>? = null,
|
||||||
|
@JsonProperty("episode_count") val episodeCount: Long? = null,
|
||||||
|
@JsonProperty("person") val person: Person? = null,
|
||||||
|
@JsonProperty("images") val images: Images? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Person(
|
||||||
|
@JsonProperty("name") val name: String? = null,
|
||||||
|
@JsonProperty("ids") val ids: Ids? = null,
|
||||||
|
@JsonProperty("images") val images: Images? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Seasons(
|
||||||
|
@JsonProperty("aired_episodes") val airedEpisodes: Int? = null,
|
||||||
|
@JsonProperty("episode_count") val episodeCount: Int? = null,
|
||||||
|
@JsonProperty("episodes") val episodes: List<TraktEpisode>? = null,
|
||||||
|
@JsonProperty("first_aired") val firstAired: String? = null,
|
||||||
|
@JsonProperty("ids") val ids: Ids? = null,
|
||||||
|
@JsonProperty("images") val images: Images? = null,
|
||||||
|
@JsonProperty("network") val network: String? = null,
|
||||||
|
@JsonProperty("number") val number: Int? = null,
|
||||||
|
@JsonProperty("overview") val overview: String? = null,
|
||||||
|
@JsonProperty("rating") val rating: Double? = null,
|
||||||
|
@JsonProperty("title") val title: String? = null,
|
||||||
|
@JsonProperty("updated_at") val updatedAt: String? = null,
|
||||||
|
@JsonProperty("votes") val votes: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TraktEpisode(
|
||||||
|
@JsonProperty("available_translations") val availableTranslations: List<String>? = null,
|
||||||
|
@JsonProperty("comment_count") val commentCount: Int? = null,
|
||||||
|
@JsonProperty("episode_type") val episodeType: String? = null,
|
||||||
|
@JsonProperty("first_aired") val firstAired: String? = null,
|
||||||
|
@JsonProperty("ids") val ids: Ids? = null,
|
||||||
|
@JsonProperty("images") val images: Images? = null,
|
||||||
|
@JsonProperty("number") val number: Int? = null,
|
||||||
|
@JsonProperty("number_abs") val numberAbs: Int? = null,
|
||||||
|
@JsonProperty("overview") val overview: String? = null,
|
||||||
|
@JsonProperty("rating") val rating: Double? = null,
|
||||||
|
@JsonProperty("runtime") val runtime: Int? = null,
|
||||||
|
@JsonProperty("season") val season: Int? = null,
|
||||||
|
@JsonProperty("title") val title: String? = null,
|
||||||
|
@JsonProperty("updated_at") val updatedAt: String? = null,
|
||||||
|
@JsonProperty("votes") val votes: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class LinkData(
|
||||||
|
val id: Int? = null,
|
||||||
|
val imdbId: String? = null,
|
||||||
|
val tvdbId: Int? = null,
|
||||||
|
val type: String? = null,
|
||||||
|
val season: Int? = null,
|
||||||
|
val episode: Int? = null,
|
||||||
|
val aniId: String? = null,
|
||||||
|
val animeId: String? = null,
|
||||||
|
val title: String? = null,
|
||||||
|
val year: Int? = null,
|
||||||
|
val orgTitle: String? = null,
|
||||||
|
val isAnime: Boolean = false,
|
||||||
|
val airedYear: Int? = null,
|
||||||
|
val lastSeason: Int? = null,
|
||||||
|
val epsTitle: String? = null,
|
||||||
|
val jpTitle: String? = null,
|
||||||
|
val date: String? = null,
|
||||||
|
val airedDate: String? = null,
|
||||||
|
val isAsian: Boolean = false,
|
||||||
|
val isBollywood: Boolean = false,
|
||||||
|
val isCartoon: Boolean = false,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.lagradost.cloudstream3.mvvm
|
||||||
|
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
|
||||||
|
/** NOTE: Only one observer at a time per value */
|
||||||
|
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
||||||
|
liveData.removeObservers(this)
|
||||||
|
liveData.observe(this) { it?.let { t -> action(t) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** NOTE: Only one observer at a time per value */
|
||||||
|
fun <T> LifecycleOwner.observeNullable(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
||||||
|
liveData.removeObservers(this)
|
||||||
|
liveData.observe(this) { action(it) }
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
package com.lagradost.cloudstream3.ui.player
|
package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.*
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.graphics.drawable.AnimatedImageDrawable
|
import android.graphics.drawable.AnimatedImageDrawable
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable
|
import android.graphics.drawable.AnimatedVectorDrawable
|
||||||
import android.media.metrics.PlaybackErrorEvent
|
import android.media.metrics.PlaybackErrorEvent
|
||||||
|
@ -24,11 +27,7 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout
|
import androidx.media3.ui.*
|
||||||
import androidx.media3.ui.DefaultTimeBar
|
|
||||||
import androidx.media3.ui.PlayerView
|
|
||||||
import androidx.media3.ui.SubtitleView
|
|
||||||
import androidx.media3.ui.TimeBar
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.github.rubensousa.previewseekbar.PreviewBar
|
import com.github.rubensousa.previewseekbar.PreviewBar
|
||||||
|
@ -442,6 +441,9 @@ abstract class AbstractPlayerFragment(
|
||||||
|
|
||||||
is VideoEndedEvent -> {
|
is VideoEndedEvent -> {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
|
// Resets subtitle delay on ended video
|
||||||
|
player.setSubtitleOffset(0)
|
||||||
|
|
||||||
// Only play next episode if autoplay is on (default)
|
// Only play next episode if autoplay is on (default)
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(ctx)
|
if (PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
?.getBoolean(
|
?.getBoolean(
|
||||||
|
|
|
@ -1118,6 +1118,9 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
Player.STATE_ENDED -> {
|
Player.STATE_ENDED -> {
|
||||||
|
// Resets subtitle delay on ended video
|
||||||
|
setSubtitleOffset(0)
|
||||||
|
|
||||||
// Only play next episode if autoplay is on (default)
|
// Only play next episode if autoplay is on (default)
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(context)
|
if (PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
?.getBoolean(
|
?.getBoolean(
|
||||||
|
|
|
@ -783,7 +783,10 @@ class ResultFragmentTv : Fragment() {
|
||||||
// resultEpisodeLoading.isVisible = episodes is Resource.Loading
|
// resultEpisodeLoading.isVisible = episodes is Resource.Loading
|
||||||
if (episodes is Resource.Success) {
|
if (episodes is Resource.Success) {
|
||||||
|
|
||||||
val lastWatchedIndex = episodes.value.indexOfLast { ep -> ep.getWatchProgress() >= NEXT_WATCH_EPISODE_PERCENTAGE.toFloat() / 100.0f }
|
val lastWatchedIndex = episodes.value.indexOfLast { ep ->
|
||||||
|
ep.getWatchProgress() >= NEXT_WATCH_EPISODE_PERCENTAGE.toFloat() / 100.0f || ep.videoWatchState == VideoWatchState.Watched
|
||||||
|
}
|
||||||
|
|
||||||
val firstUnwatched = episodes.value.getOrElse(lastWatchedIndex + 1) { episodes.value.firstOrNull() }
|
val firstUnwatched = episodes.value.getOrElse(lastWatchedIndex + 1) { episodes.value.firstOrNull() }
|
||||||
|
|
||||||
if (firstUnwatched != null) {
|
if (firstUnwatched != null) {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package com.lagradost.cloudstream3.ui.settings
|
package com.lagradost.cloudstream3.ui.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
@ -18,12 +18,14 @@ import com.lagradost.cloudstream3.BuildConfig
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.MainSettingsBinding
|
import com.lagradost.cloudstream3.databinding.MainSettingsBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
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.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
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
|
@ -133,7 +135,6 @@ class SettingsFragment : Fragment() {
|
||||||
val localBinding = MainSettingsBinding.inflate(inflater, container, false)
|
val localBinding = MainSettingsBinding.inflate(inflater, container, false)
|
||||||
binding = localBinding
|
binding = localBinding
|
||||||
return localBinding.root
|
return localBinding.root
|
||||||
//return inflater.inflate(R.layout.main_settings, container, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -141,21 +142,44 @@ class SettingsFragment : Fragment() {
|
||||||
activity?.navigate(id, Bundle())
|
activity?.navigate(id, Bundle())
|
||||||
}
|
}
|
||||||
|
|
||||||
// used to debug leaks showToast(activity,"${VideoDownloadManager.downloadStatusEvent.size} : ${VideoDownloadManager.downloadProgressEvent.size}")
|
/** used to debug leaks
|
||||||
|
showToast(activity,"${VideoDownloadManager.downloadStatusEvent.size} :
|
||||||
|
${VideoDownloadManager.downloadProgressEvent.size}") **/
|
||||||
|
|
||||||
for (syncApi in accountManagers) {
|
fun hasProfilePictureFromAccountManagers(accountManagers: List<AccountManager>): Boolean {
|
||||||
val login = syncApi.loginInfo()
|
for (syncApi in accountManagers) {
|
||||||
val pic = login?.profilePicture ?: continue
|
val login = syncApi.loginInfo()
|
||||||
if (binding?.settingsProfilePic?.setImage(
|
val pic = login?.profilePicture ?: continue
|
||||||
pic,
|
|
||||||
errorImageDrawable = HomeFragment.errorProfilePic
|
if (binding?.settingsProfilePic?.setImage(
|
||||||
) == true
|
pic,
|
||||||
) {
|
errorImageDrawable = HomeFragment.errorProfilePic
|
||||||
binding?.settingsProfileText?.text = login.name
|
) == true
|
||||||
binding?.settingsProfile?.isVisible = true
|
) {
|
||||||
break
|
binding?.settingsProfileText?.text = login.name
|
||||||
|
return true // sync profile exists
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false // not syncing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// display local account information if not syncing
|
||||||
|
if (!hasProfilePictureFromAccountManagers(accountManagers)) {
|
||||||
|
val activity = activity ?: return
|
||||||
|
val currentAccount = try {
|
||||||
|
DataStoreHelper.accounts.firstOrNull {
|
||||||
|
it.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||||
|
} ?: activity.let { DataStoreHelper.getDefaultAccount(activity) }
|
||||||
|
|
||||||
|
} catch (t: IllegalStateException) {
|
||||||
|
Log.e("AccountManager", "Activity not found", t)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
binding?.settingsProfilePic?.setImage(currentAccount?.image)
|
||||||
|
binding?.settingsProfileText?.text = currentAccount?.name
|
||||||
|
}
|
||||||
|
|
||||||
binding?.apply {
|
binding?.apply {
|
||||||
listOf(
|
listOf(
|
||||||
settingsGeneral to R.id.action_navigation_global_to_navigation_settings_general,
|
settingsGeneral to R.id.action_navigation_global_to_navigation_settings_general,
|
||||||
|
|
|
@ -50,7 +50,7 @@ class JsUnpacker(packedJS: String?) {
|
||||||
throw Exception("Unknown p.a.c.k.e.r. encoding")
|
throw Exception("Unknown p.a.c.k.e.r. encoding")
|
||||||
}
|
}
|
||||||
val unbase = Unbase(radix)
|
val unbase = Unbase(radix)
|
||||||
p = Pattern.compile("\\b\\w+\\b")
|
p = Pattern.compile("""\b[a-zA-Z0-9_]+\b""")
|
||||||
m = p.matcher(payload)
|
m = p.matcher(payload)
|
||||||
val decoded = StringBuilder(payload)
|
val decoded = StringBuilder(payload)
|
||||||
var replaceOffset = 0
|
var replaceOffset = 0
|
||||||
|
|
13
app/src/main/res/drawable/rounded_outline.xml
Normal file
13
app/src/main/res/drawable/rounded_outline.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@android:color/white">
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="?attr/white" />
|
||||||
|
<corners android:radius="10dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
|
@ -24,7 +24,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="20dp"
|
android:padding="20dp"
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
|
@ -36,7 +35,11 @@
|
||||||
android:id="@+id/settings_profile_pic"
|
android:id="@+id/settings_profile_pic"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:ignore="ContentDescription" />
|
android:scaleType="centerCrop"
|
||||||
|
android:foreground="@drawable/rounded_outline"
|
||||||
|
tools:src="@drawable/profile_bg_orange"
|
||||||
|
android:contentDescription="@string/account"/>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -50,7 +53,7 @@
|
||||||
android:textColor="?attr/textColor"
|
android:textColor="?attr/textColor"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:textStyle="normal"
|
android:textStyle="normal"
|
||||||
tools:text="Hello world" />
|
tools:text="Quick Brown Fox" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
|
@ -8,6 +8,8 @@ buildscript {
|
||||||
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.22")
|
||||||
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
|
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
|
||||||
|
// Universal build config
|
||||||
|
classpath("com.codingfeline.buildkonfig:buildkonfig-gradle-plugin:0.15.1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +24,6 @@ plugins {
|
||||||
id("com.google.devtools.ksp") version "1.9.22-1.0.17" apply false
|
id("com.google.devtools.ksp") version "1.9.22-1.0.17" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
//tasks.register<Delete>("clean") {
|
||||||
delete(rootProject.layout.buildDirectory)
|
// delete(rootProject.layout.buildDirectory)
|
||||||
}
|
//}
|
||||||
|
|
68
library/build.gradle.kts
Normal file
68
library/build.gradle.kts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import com.codingfeline.buildkonfig.compiler.FieldSpec
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
id("maven-publish")
|
||||||
|
id("com.android.library")
|
||||||
|
id("com.codingfeline.buildkonfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
version = "1.0.0"
|
||||||
|
androidTarget()
|
||||||
|
jvm()
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain.dependencies {
|
||||||
|
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser
|
||||||
|
^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API
|
||||||
|
Level 25 or Less. */
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
maven("https://jitpack.io")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||||
|
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
buildkonfig {
|
||||||
|
packageName = "com.lagradost.api"
|
||||||
|
exposeObjectWithName = "BuildConfig"
|
||||||
|
|
||||||
|
defaultConfigs {
|
||||||
|
val isDebug = kotlin.runCatching { extra.get("isDebug") }.getOrNull() == true
|
||||||
|
buildConfigField(FieldSpec.Type.BOOLEAN, "DEBUG", isDebug.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk = 34
|
||||||
|
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
targetSdk = 33
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the same com.lagradost.cloudstream3.R stops working
|
||||||
|
namespace = "com.lagradost.api"
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
withType<MavenPublication> {
|
||||||
|
groupId = "com.lagradost.api"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
library/src/androidMain/AndroidManifest.xml
Normal file
2
library/src/androidMain/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest />
|
21
library/src/androidMain/kotlin/com/lagradost/api/Log.kt
Normal file
21
library/src/androidMain/kotlin/com/lagradost/api/Log.kt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package com.lagradost.api
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
actual object Log {
|
||||||
|
actual fun d(tag: String, message: String) {
|
||||||
|
Log.d(tag, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun i(tag: String, message: String) {
|
||||||
|
Log.i(tag, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun w(tag: String, message: String) {
|
||||||
|
Log.w(tag, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun e(tag: String, message: String) {
|
||||||
|
Log.e(tag, message)
|
||||||
|
}
|
||||||
|
}
|
8
library/src/commonMain/kotlin/com/lagradost/api/Log.kt
Normal file
8
library/src/commonMain/kotlin/com/lagradost/api/Log.kt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package com.lagradost.api
|
||||||
|
|
||||||
|
expect object Log {
|
||||||
|
fun d(tag: String, message: String)
|
||||||
|
fun i(tag: String, message: String)
|
||||||
|
fun w(tag: String, message: String)
|
||||||
|
fun e(tag: String, message: String)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.lagradost.cloudstream3
|
||||||
|
|
||||||
|
class ErrorLoadingException(message: String? = null) : Exception(message)
|
|
@ -1,10 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.mvvm
|
package com.lagradost.cloudstream3.mvvm
|
||||||
|
|
||||||
import android.util.Log
|
import com.lagradost.api.BuildConfig
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import com.lagradost.api.Log
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import com.bumptech.glide.load.HttpException
|
|
||||||
import com.lagradost.cloudstream3.BuildConfig
|
|
||||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.io.InterruptedIOException
|
import java.io.InterruptedIOException
|
||||||
|
@ -49,18 +46,6 @@ inline fun debugWarning(assert: () -> Boolean, message: () -> String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** NOTE: Only one observer at a time per value */
|
|
||||||
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
|
||||||
liveData.removeObservers(this)
|
|
||||||
liveData.observe(this) { it?.let { t -> action(t) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** NOTE: Only one observer at a time per value */
|
|
||||||
fun <T> LifecycleOwner.observeNullable(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
|
||||||
liveData.removeObservers(this)
|
|
||||||
liveData.observe(this) { action(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Resource<out T> {
|
sealed class Resource<out T> {
|
||||||
data class Success<out T>(val value: T) : Resource<T>()
|
data class Success<out T>(val value: T) : Resource<T>()
|
||||||
data class Failure(
|
data class Failure(
|
||||||
|
@ -158,14 +143,14 @@ fun<T> throwAbleToResource(
|
||||||
"Connection Timeout\nPlease try again later."
|
"Connection Timeout\nPlease try again later."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HttpException -> {
|
// is HttpException -> {
|
||||||
Resource.Failure(
|
// Resource.Failure(
|
||||||
false,
|
// false,
|
||||||
throwable.statusCode,
|
// throwable.statusCode,
|
||||||
null,
|
// null,
|
||||||
throwable.message ?: "HttpException"
|
// throwable.message ?: "HttpException"
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
is UnknownHostException -> {
|
is UnknownHostException -> {
|
||||||
Resource.Failure(true, null, null, "Cannot connect to server, try again later.\n${throwable.message}")
|
Resource.Failure(true, null, null, "Cannot connect to server, try again later.\n${throwable.message}")
|
||||||
}
|
}
|
19
library/src/jvmMain/kotlin/com/lagradost/api/Log.kt
Normal file
19
library/src/jvmMain/kotlin/com/lagradost/api/Log.kt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package com.lagradost.api
|
||||||
|
|
||||||
|
actual object Log {
|
||||||
|
actual fun d(tag: String, message: String) {
|
||||||
|
println("DEBUG $tag: $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun i(tag: String, message: String) {
|
||||||
|
println("INFO $tag: $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun w(tag: String, message: String) {
|
||||||
|
println("WARNING $tag: $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun e(tag: String, message: String) {
|
||||||
|
println("ERROR $tag: $message")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
rootProject.name = "CloudStream"
|
rootProject.name = "CloudStream"
|
||||||
|
|
||||||
include(":app")
|
include(":app")
|
||||||
|
include(":library")
|
Loading…
Reference in a new issue