diff --git a/README.md b/README.md index e74a8121..6a8917f5 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # CloudStream-3 +![GitHub release](https://img.shields.io/github/v/release/LagradOst/cloudstream-3?sort=semver&style=for-the-badge) +[![Discord](https://img.shields.io/discord/737724143126052974?style=for-the-badge)](https://discord.gg/5Hus6fM) + + **DOWNLOAD:** https://github.com/LagradOst/CloudStream-3/releases -**Discord:** -https://discord.gg/5Hus6fM ***Features:*** + **AdFree**, No ads whatsoever @@ -27,6 +29,7 @@ https://discord.gg/5Hus6fM * 🇪🇸 Spanish * 🇳🇴 Norsk * 🇩🇪 German +* 🇹🇷 Turkish ***SCREENSHOTS:*** @@ -49,6 +52,7 @@ It merely scrapes 3rd-party websites that are publicly accessable via any regula - [gogoanime.vc](https://gogoanime.vc) +- [allanime.site](https://allanime.site) - [animeflick.net](https://animeflick.net) - [kawaiifu.com](https://kawaiifu.com) - [tenshi.moe](https://tenshi.moe) diff --git a/app/build.gradle b/app/build.gradle index 27773e78..a8cb5578 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,9 @@ android { applicationId "com.lagradost.cloudstream3" minSdkVersion 21 targetSdkVersion 31 - versionCode 31 - versionName "2.1.2" + + versionCode 33 + versionName "2.2.2" resValue "string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7f8694ec..722a8329 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,10 +10,16 @@ + + + + @@ -63,7 +70,7 @@ android:enabled="false" android:exported="true"> - + ): List { } /** https://www.imdb.com/title/tt2861424/ -> tt2861424 */ -fun imdbUrlToId(url: String): String { - return url - .removePrefix("https://www.imdb.com/title/") - .removePrefix("https://imdb.com/title/tt2861424/") - .replace("/", "") +fun imdbUrlToId(url: String): String? { + return Regex("/title/(tt[0-9]*)").find(url)?.groupValues?.get(1) + ?: Regex("tt[0-9]{5,}").find(url)?.groupValues?.get(0) } fun imdbUrlToIdNullable(url: String?): String? { @@ -392,12 +391,12 @@ fun LoadResponse?.isAnimeBased(): Boolean { data class AnimeEpisode( val url: String, - val name: String? = null, - val posterUrl: String? = null, - val date: String? = null, - val rating: Int? = null, - val descript: String? = null, - val episode: Int? = null, + var name: String? = null, + var posterUrl: String? = null, + var date: String? = null, + var rating: Int? = null, + var description: String? = null, + var episode: Int? = null, ) data class TorrentLoadResponse( @@ -418,32 +417,47 @@ data class TorrentLoadResponse( ) : LoadResponse data class AnimeLoadResponse( - val engName: String?, - val japName: String?, + var engName: String? = null, + var japName: String? = null, override val name: String, override val url: String, override val apiName: String, override val type: TvType, - override val posterUrl: String?, - override val year: Int?, + override var posterUrl: String? = null, + override var year: Int? = null, - val dubEpisodes: List?, - val subEpisodes: List?, - val showStatus: ShowStatus?, + var episodes: HashMap> = hashMapOf(), + var showStatus: ShowStatus? = null, - override val plot: String?, - override val tags: List? = null, - val synonyms: List? = null, + override var plot: String? = null, + override var tags: List? = null, + var synonyms: List? = null, - val malId: Int? = null, - val anilistId: Int? = null, - override val rating: Int? = null, - override val duration: String? = null, - override val trailerUrl: String? = null, - override val recommendations: List? = null, + var malId: Int? = null, + var anilistId: Int? = null, + override var rating: Int? = null, + override var duration: String? = null, + override var trailerUrl: String? = null, + override var recommendations: List? = null, ) : LoadResponse +fun AnimeLoadResponse.addEpisodes(status : DubStatus, episodes : List?) { + if(episodes == null) return + this.episodes[status] = episodes +} + +fun MainAPI.newAnimeLoadResponse( + name: String, + url: String, + type: TvType, + initializer: AnimeLoadResponse.() -> Unit = { } +): AnimeLoadResponse { + val builder = AnimeLoadResponse(name = name, url = url, apiName = this.name, type = type) + builder.initializer() + return builder +} + data class MovieLoadResponse( override val name: String, override val url: String, @@ -451,11 +465,11 @@ data class MovieLoadResponse( override val type: TvType, val dataUrl: String, - override val posterUrl: String?, - override val year: Int?, - override val plot: String?, + override val posterUrl: String? = null, + override val year: Int? = null, + override val plot: String? = null, - val imdbId: String?, + val imdbId: String? = null, override val rating: Int? = null, override val tags: List? = null, override val duration: String? = null, @@ -464,9 +478,9 @@ data class MovieLoadResponse( ) : LoadResponse data class TvSeriesEpisode( - val name: String?, - val season: Int?, - val episode: Int?, + val name: String? = null, + val season: Int? = null, + val episode: Int? = null, val data: String, val posterUrl: String? = null, val date: String? = null, @@ -481,12 +495,12 @@ data class TvSeriesLoadResponse( override val type: TvType, val episodes: List, - override val posterUrl: String?, - override val year: Int?, - override val plot: String?, + override val posterUrl: String? = null, + override val year: Int? = null, + override val plot: String? = null, - val showStatus: ShowStatus?, - val imdbId: String?, + val showStatus: ShowStatus? = null, + val imdbId: String? = null, override val rating: Int? = null, override val tags: List? = null, override val duration: String? = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 0c9a956d..d7a8c6a8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -31,6 +31,7 @@ import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.player.PlayerEventType +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.DataStore.getKey @@ -160,16 +161,16 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD, KeyEvent.KEYCODE_MEDIA_REWIND -> { PlayerEventType.SeekBack } - KeyEvent.KEYCODE_MEDIA_NEXT -> { + KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1 -> { PlayerEventType.NextEpisode } - KeyEvent.KEYCODE_MEDIA_PREVIOUS -> { + KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1 -> { PlayerEventType.PrevEpisode } KeyEvent.KEYCODE_MEDIA_PAUSE -> { PlayerEventType.Pause } - KeyEvent.KEYCODE_MEDIA_PLAY -> { + KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_BUTTON_START -> { PlayerEventType.Play } KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_NUMPAD_7 -> { @@ -184,13 +185,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_NUMPAD_9 -> { PlayerEventType.ShowMirrors } - KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_NUMPAD_3 -> { + KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_NUMPAD_3 -> { PlayerEventType.ShowSpeed } KeyEvent.KEYCODE_R, KeyEvent.KEYCODE_NUMPAD_0 -> { PlayerEventType.Resize } - KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE -> { // space is not captured due to navigation + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation PlayerEventType.PlayPauseToggle } else -> null @@ -198,6 +199,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { playerEventListener?.invoke(playerEvent) } + //when (keyCode) { + // KeyEvent.KEYCODE_DPAD_CENTER -> { + // println("DPAD PRESSED ${this.isKeyboardOpen()}") + // } + //} + return super.onKeyDown(keyCode, event) } @@ -354,10 +361,35 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } override fun onCreate(savedInstanceState: Bundle?) { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + + val currentTheme = when (settingsManager.getString(getString(R.string.app_theme_key), "Black")) { + "Black" -> R.style.AppTheme + "Light" -> R.style.LightMode + "Amoled" -> R.style.AmoledMode + else -> R.style.AppTheme + } + + val currentOverlayTheme = when (settingsManager.getString(getString(R.string.primary_color_key), "Normal")) { + "Normal" -> R.style.OverlayPrimaryColorNormal + "Blue" -> R.style.OverlayPrimaryColorBlue + "Purple" -> R.style.OverlayPrimaryColorPurple + "Green" -> R.style.OverlayPrimaryColorGreen + "GreenApple" -> R.style.OverlayPrimaryColorGreenApple + "Red" -> R.style.OverlayPrimaryColorRed + "Banana" -> R.style.OverlayPrimaryColorBanana + "Party" -> R.style.OverlayPrimaryColorParty + else -> R.style.OverlayPrimaryColorNormal + } + + theme.applyStyle(currentTheme, true) + theme.applyStyle(currentOverlayTheme, true) + theme.applyStyle( R.style.LoadedStyle, true ) // THEME IS SET BEFORE VIEW IS CREATED TO APPLY THE THEME TO THE MAIN VIEW + updateLocale() initRequestClient() super.onCreate(savedInstanceState) @@ -371,7 +403,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) - setContentView(R.layout.activity_main) + if (isTvSettings()) { + setContentView(R.layout.activity_main_tv) + } else { + setContentView(R.layout.activity_main) + } + // val navView: BottomNavigationView = findViewById(R.id.nav_view) //https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt new file mode 100644 index 00000000..19cbe536 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AllAnimeProvider.kt @@ -0,0 +1,320 @@ +package com.lagradost.cloudstream3.animeproviders + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.module.kotlin.readValue +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.network.get +import com.lagradost.cloudstream3.network.text +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.cloudstream3.utils.getQualityFromName +import org.jsoup.Jsoup +import org.mozilla.javascript.Context +import org.mozilla.javascript.Scriptable +import java.net.URI +import java.net.URLDecoder +import java.util.* + + +class AllAnimeProvider : MainAPI() { + override val mainUrl: String + get() = "https://allanime.site" + override val name: String + get() = "AllAnime" + override val hasQuickSearch: Boolean + get() = false + override val hasMainPage: Boolean + get() = false + + private val hlsHelper = M3u8Helper() + + private fun getStatus(t: String): ShowStatus { + return when (t) { + "Finished" -> ShowStatus.Completed + "Releasing" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + + override val supportedTypes: Set + get() = setOf(TvType.Anime, TvType.AnimeMovie) + + private data class Data( + @JsonProperty("shows") val shows: Shows + ) + + private data class Shows( + @JsonProperty("pageInfo") val pageInfo: PageInfo, + @JsonProperty("edges") val edges: List, + @JsonProperty("__typename") val _typename: String + ) + + private data class Edges( + @JsonProperty("_id") val Id: String?, + @JsonProperty("name") val name: String, + @JsonProperty("englishName") val englishName: String?, + @JsonProperty("nativeName") val nativeName: String?, + @JsonProperty("thumbnail") val thumbnail: String?, + @JsonProperty("type") val type: String?, + @JsonProperty("season") val season: Season?, + @JsonProperty("score") val score: Double?, + @JsonProperty("airedStart") val airedStart: AiredStart?, + @JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes?, + @JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AvailableEpisodesDetail?, + @JsonProperty("studios") val studios: List?, + @JsonProperty("description") val description: String?, + @JsonProperty("status") val status: String?, + ) + + private data class AvailableEpisodes( + @JsonProperty("sub") val sub: Int, + @JsonProperty("dub") val dub: Int, + @JsonProperty("raw") val raw: Int + ) + + private data class AiredStart( + @JsonProperty("year") val year: Int, + @JsonProperty("month") val month: Int, + @JsonProperty("date") val date: Int + ) + + private data class Season( + @JsonProperty("quarter") val quarter: String, + @JsonProperty("year") val year: Int + ) + + private data class PageInfo( + @JsonProperty("total") val total: Int, + @JsonProperty("__typename") val _typename: String + ) + + private data class AllAnimeQuery( + @JsonProperty("data") val data: Data + ) + + override fun search(query: String): ArrayList { + val link = + """$mainUrl/graphql?variables=%7B%22search%22%3A%7B%22allowAdult%22%3Afalse%2C%22query%22%3A%22$query%22%7D%2C%22limit%22%3A26%2C%22page%22%3A1%2C%22translationType%22%3A%22sub%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%229343797cc3d9e3f444e2d3b7db9a84d759b816a4d84512ea72d079f85bb96e98%22%7D%7D""" + var res = get(link).text + if (res.contains("PERSISTED_QUERY_NOT_FOUND")) { + res = get(link).text + if (res.contains("PERSISTED_QUERY_NOT_FOUND")) return ArrayList() + } + val response = mapper.readValue(res) + + val results = response.data.shows.edges.filter { + // filtering in case there is an anime with 0 episodes available on the site. + !(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0) + } + + return ArrayList(results.map { + AnimeSearchResponse( + it.name, + "$mainUrl/anime/${it.Id}", + this.name, + TvType.Anime, + it.thumbnail, + it.airedStart?.year, + EnumSet.of(DubStatus.Subbed, DubStatus.Dubbed), + it.englishName, + it.availableEpisodes?.dub, + it.availableEpisodes?.sub + ) + }) + } + + private data class AvailableEpisodesDetail( + @JsonProperty("sub") val sub: List, + @JsonProperty("dub") val dub: List, + @JsonProperty("raw") val raw: List + ) + + + override fun load(url: String): LoadResponse? { + val rhino = Context.enter() + rhino.initStandardObjects() + rhino.optimizationLevel = -1 + val scope: Scriptable = rhino.initStandardObjects() + + val html = get(url).text + val soup = Jsoup.parse(html) + + val script = soup.select("script").firstOrNull { + it.html().contains("window.__NUXT__") + } ?: return null + + val js = """ + const window = {} + ${script.html()} + const returnValue = JSON.stringify(window.__NUXT__.fetch[0].show) + """.trimIndent() + + rhino.evaluateString(scope, js, "JavaScript", 1, null) + val jsEval = scope.get("returnValue", scope) ?: return null + val showData = mapper.readValue(jsEval as String) + + val title = showData.name + val description = showData.description + val poster = showData.thumbnail + + val episodes = showData.availableEpisodes.let { + if (it == null) return@let Pair(null, null) + Pair(if (it.sub != 0) ArrayList((1..it.sub).map { epNum -> + AnimeEpisode( + "$mainUrl/anime/${showData.Id}/episodes/sub/$epNum", episode = epNum + ) + }) else null, if (it.dub != 0) ArrayList((1..it.dub).map { epNum -> + AnimeEpisode( + "$mainUrl/anime/${showData.Id}/episodes/dub/$epNum", episode = epNum + ) + }) else null) + } + + return newAnimeLoadResponse(title, url, TvType.Anime) { + posterUrl = poster + year = showData.airedStart?.year + + addEpisodes(DubStatus.Subbed, episodes.first) + addEpisodes(DubStatus.Dubbed, episodes.second) + + showStatus = getStatus(showData.status.toString()) + + plot = description?.replace(Regex("""<(.*?)>"""), "") + } + } + + private val embedBlackList = listOf( + "https://mp4upload.com/", + "https://streamsb.net/", + "https://dood.to/", + "https://videobin.co/", + "https://ok.ru", + "https://streamlare.com", + ) + + private fun embedIsBlacklisted(url: String): Boolean { + embedBlackList.forEach { + if (it.javaClass.name == "kotlin.text.Regex") { + if ((it as Regex).matches(url)) { + return true + } + } else { + if (url.contains(it)) { + return true + } + } + } + return false + } + + private fun String.sanitize(): String { + var out = this + listOf(Pair("\\u002F", "/")).forEach { + out = out.replace(it.first, it.second) + } + return out + } + + private data class Links( + @JsonProperty("link") val link: String, + @JsonProperty("hls") val hls: Boolean?, + @JsonProperty("resolutionStr") val resolutionStr: String, + @JsonProperty("src") val src: String? + ) + + private data class AllAnimeVideoApiResponse( + @JsonProperty("links") val links: List + ) + + private data class ApiEndPoint( + @JsonProperty("episodeIframeHead") val episodeIframeHead: String + ) + + private fun getM3u8Qualities(m3u8Link: String, referer: String, qualityName: String): ArrayList { + return ArrayList(hlsHelper.m3u8Generation(M3u8Helper.M3u8Stream(m3u8Link, null), true).map { stream -> + val qualityString = if ((stream.quality ?: 0) == 0) "" else "${stream.quality}p" + ExtractorLink( + this.name, + "${this.name} - $qualityName $qualityString", + stream.streamUrl, + referer, + getQualityFromName(stream.quality.toString()), + true, + stream.headers + ) + }) + } + + override fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + var apiEndPoint = mapper.readValue(get("$mainUrl/getVersion").text).episodeIframeHead + if (apiEndPoint.endsWith("/")) apiEndPoint = apiEndPoint.slice(0 until apiEndPoint.length - 1) + + val html = get(data).text + + val sources = Regex("""sourceUrl[:=]"(.+?)"""").findAll(html).toList() + .map { URLDecoder.decode(it.destructured.component1().sanitize(), "UTF-8") } + sources.forEach { + var link = it + if (URI(link).isAbsolute || link.startsWith("//")) { + if (link.startsWith("//")) link = "https:$it" + + if (Regex("""streaming\.php\?""").matches(link)) { + // for now ignore + } else if (!embedIsBlacklisted(link)) { + if (URI(link).path.contains(".m3u")) { + getM3u8Qualities(link, data, URI(link).host).forEach(callback) + } else { + callback( + ExtractorLink( + "AllAnime - " + URI(link).host, + "", + link, + data, + getQualityFromName("1080"), + false + ) + ) + } + } + } else { + link = apiEndPoint + URI(link).path + ".json?" + URI(link).query + val response = get(link) + + if (response.code < 400) { + val links = mapper.readValue(response.text).links + links.forEach { server -> + if (server.hls != null && server.hls) { + getM3u8Qualities( + server.link, + "$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI( + server.link + ).path), + server.resolutionStr + ).forEach(callback) + } else { + callback( + ExtractorLink( + "AllAnime - " + URI(server.link).host, + server.resolutionStr, + server.link, + "$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI( + server.link + ).path), + getQualityFromName("1080"), + false + ) + ) + } + } + } + } + } + return true + } + +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeFlickProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeFlickProvider.kt index e18dc610..eabecc4e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeFlickProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimeFlickProvider.kt @@ -76,21 +76,15 @@ class AnimeFlickProvider : MainAPI() { AnimeEpisode(link, name) }.reversed() - return AnimeLoadResponse( - title, - null, - title, - url, - this.name, - getType(title), - poster, - year, - null, - episodes, - null, - description, - genres - ) + return newAnimeLoadResponse(title, url, getType(title)) { + posterUrl = poster + this.year = year + + addEpisodes(DubStatus.Subbed, episodes) + + plot = description + tags = genres + } } override fun loadLinks( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimePaheProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimePaheProvider.kt index b056bbbd..13187b8b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimePaheProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AnimePaheProvider.kt @@ -293,30 +293,26 @@ class AnimePaheProvider : MainAPI() { } } - AnimeLoadResponse( - animeTitle, - japTitle, - animeTitle, - url, - this.name, - getType(tvType.toString()), - poster, - year, - null, - episodes, - status, - synopsis, - if (!doc.select(".anime-genre > ul a").isEmpty()) { + newAnimeLoadResponse(animeTitle, url, getType(tvType.toString())) { + engName = animeTitle + japName = japTitle + + this.posterUrl = poster + this.year = year + + addEpisodes(DubStatus.Subbed, episodes) + this.showStatus = status + plot = synopsis + tags = if (!doc.select(".anime-genre > ul a").isEmpty()) { ArrayList(doc.select(".anime-genre > ul a").map { it.text().toString() }) } else { null - }, - ArrayList(), - malId, - anilistId, - null, - trailer - ) + } + + this.malId = malId + this.anilistId = anilistId + this.trailerUrl = trailer + } } } @@ -325,7 +321,6 @@ class AnimePaheProvider : MainAPI() { return s?.toIntOrNull() != null } - private fun cookieStrToMap(cookie: String): Map { val cookies = mutableMapOf() for (string in cookie.split("; ")) { @@ -347,7 +342,6 @@ class AnimePaheProvider : MainAPI() { val slice2 = characterMap.slice(0 until s2) var acc: Long = 0 - for ((n, i) in content.reversed().withIndex()) { acc += (when (isNumber("$i")) { true -> "$i".toLong() diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DubbedAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DubbedAnimeProvider.kt index 80f22b9a..d3a4e277 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DubbedAnimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/DubbedAnimeProvider.kt @@ -109,7 +109,7 @@ class DubbedAnimeProvider : MainAPI() { HomePageList("Trending", parseDocumentTrending(trendingUrl)), HomePageList("Recently Added", parseDocument(recentlyAddedUrl)), HomePageList("Recent Releases", parseDocument(lastEpisodeUrl, true)), - // HomePageList("All", parseDocument(allUrl)) + // HomePageList("All", parseDocument(allUrl)) ) return HomePageResponse(listItems) @@ -268,20 +268,12 @@ class DubbedAnimeProvider : MainAPI() { } val img = fixUrl(document.select("div.fkimgs > img").attr("src")) - return AnimeLoadResponse( - null, - null, - title, - url, - this.name, - TvType.Anime, - img, - year, - ArrayList(episodes), - null, - null, - descript, - ) + return newAnimeLoadResponse(title, url, TvType.Anime) { + posterUrl = img + this.year = year + addEpisodes(DubStatus.Dubbed, episodes) + plot = descript + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt index 2f14b8dd..3092e949 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt @@ -185,24 +185,18 @@ class GogoanimeProvider : MainAPI() { "Episode " + it.selectFirst(".name").text().replace("EP", "").trim() ) }.reversed() - return AnimeLoadResponse( - title, - nativeName, - title, - link, - this.name, - getType(type.toString()), - poster, - year, - null, - episodes, - getStatus(status.toString()), - description, - ArrayList(genre), - null, - null, - null, - ) + + return newAnimeLoadResponse(title, link, getType(type.toString())) { + japName = nativeName + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) // TODO CHECK + plot = description + tags = genre + + showStatus = getStatus(status.toString()) + } } private fun extractVideos(uri: String): List { diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KawaiifuProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KawaiifuProvider.kt index d8de465a..cde4f208 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KawaiifuProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/KawaiifuProvider.kt @@ -109,23 +109,13 @@ class KawaiifuProvider : MainAPI() { } val poster = soup.selectFirst("a.thumb > img").attr("src") - - return AnimeLoadResponse( - title, - null, - title, - url, - this.name, - TvType.Anime, - poster, - year, - null, - episodes, - ShowStatus.Ongoing, - description, - ArrayList(tags), - ArrayList() - ) + return newAnimeLoadResponse(title, url, TvType.Anime) { + this.year = year + posterUrl = poster + addEpisodes(DubStatus.Subbed, episodes) + plot = description + this.tags = tags + } } override fun loadLinks( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/TenshiProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/TenshiProvider.kt index 95d10a37..2bec47c6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/TenshiProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/TenshiProvider.kt @@ -268,24 +268,19 @@ class TenshiProvider : MainAPI() { val synonyms = document.select("li.synonym.meta-data > div.info-box > span.value").map { it?.text()?.trim().toString() } - return AnimeLoadResponse( - englishTitle, - japaneseTitle, - canonicalTitle, - url, - this.name, - getType(type ?: ""), - poster, - year.toIntOrNull(), - null, - episodes, - status, - synopsis, - ArrayList(genre), - ArrayList(synonyms), - null, - null, - ) + return newAnimeLoadResponse(canonicalTitle,url,getType(type ?: "")) { + engName = englishTitle + japName = japaneseTitle + + posterUrl = poster + this.year = year.toIntOrNull() + + addEpisodes(DubStatus.Subbed,episodes) + showStatus = status + tags = genre + this.synonyms = synonyms + plot = synopsis + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/WcoProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/WcoProvider.kt index fe127565..6c96c92f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/WcoProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/WcoProvider.kt @@ -208,22 +208,16 @@ class WcoProvider : MainAPI() { val genre = document.select("div.elements div.row > div:nth-child(1) > div.row-line:nth-child(5) > a") .map { it?.text()?.trim().toString() } - return AnimeLoadResponse( - canonicalTitle, - japaneseTitle, - canonicalTitle, - url, - this.name, - getType(type ?: ""), - poster, - year, - if (isDubbed) episodes else null, - if (!isDubbed) episodes else null, - status, - synopsis, - ArrayList(genre), - ArrayList(), - ) + return newAnimeLoadResponse(canonicalTitle,url,getType(type ?: "")) { + japName = japaneseTitle + engName = canonicalTitle + posterUrl = poster + this.year = year + addEpisodes(if(isDubbed) DubStatus.Dubbed else DubStatus.Subbed,episodes) + showStatus = status + plot = synopsis + tags = genre + } } override fun loadLinks( diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt index f13d79e3..21ac1511 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt @@ -230,21 +230,17 @@ class ZoroProvider : MainAPI() { it.selectFirst(".ssli-order")?.text()?.toIntOrNull() ) } - return AnimeLoadResponse( - title, - japaneseTitle, - title, - url, - this.name, - TvType.Anime, - poster, - year, - null, - episodes, - status, - description, - tags, - ) + + return newAnimeLoadResponse(title, url, TvType.Anime) { + japName = japaneseTitle + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + this.tags = tags + } } private fun getM3u8FromRapidCloud(url: String): String { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt index 9c694e68..2432c855 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt @@ -36,11 +36,13 @@ object DownloadButtonSetup { builder.setTitle(R.string.delete_file) .setMessage( - ctx.getString(R.string.delete_message).format(ctx.getNameFull( - click.data.name, - click.data.episode, - click.data.season - )) + ctx.getString(R.string.delete_message).format( + ctx.getNameFull( + click.data.name, + click.data.episode, + click.data.season + ) + ) ) .setPositiveButton(R.string.delete, dialogClickListener) .setNegativeButton(R.string.cancel, dialogClickListener) @@ -54,13 +56,19 @@ object DownloadButtonSetup { } DOWNLOAD_ACTION_RESUME_DOWNLOAD -> { activity?.let { ctx -> - val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id) - if (pkg != null) { - VideoDownloadManager.downloadFromResumeUsingWorker(ctx, pkg) - } else { + if (VideoDownloadManager.downloadStatus.containsKey(id) && VideoDownloadManager.downloadStatus[id] == VideoDownloadManager.DownloadType.IsPaused) { VideoDownloadManager.downloadEvent.invoke( Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume) ) + } else { + val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id) + if (pkg != null) { + VideoDownloadManager.downloadFromResumeUsingWorker(ctx, pkg) + } else { + VideoDownloadManager.downloadEvent.invoke( + Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume) + ) + } } } } @@ -69,7 +77,7 @@ object DownloadButtonSetup { val length = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(act, click.data.id)?.fileLength ?: 0 - if(length > 0) { + if (length > 0) { MainActivity.showToast(act, R.string.delete, Toast.LENGTH_LONG) } else { MainActivity.showToast(act, R.string.download, Toast.LENGTH_LONG) @@ -86,19 +94,21 @@ object DownloadButtonSetup { click.data.id.toString() ) ?: return - act.navigate(R.id.global_to_navigation_player, PlayerFragment.newInstance( - UriData( - info.path.toString(), - keyInfo.relativePath, - keyInfo.displayName, - click.data.parentId, - click.data.id, - headerName ?: "null", - if (click.data.episode <= 0) null else click.data.episode, - click.data.season - ), - act.getViewPos(click.data.id)?.position ?: 0 - )) + act.navigate( + R.id.global_to_navigation_player, PlayerFragment.newInstance( + UriData( + info.path.toString(), + keyInfo.relativePath, + keyInfo.displayName, + click.data.parentId, + click.data.id, + headerName ?: "null", + if (click.data.episode <= 0) null else click.data.episode, + click.data.season + ), + act.getViewPos(click.data.id)?.position ?: 0 + ) + ) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index 13cf079d..c3c6e14c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider @@ -15,8 +16,9 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick +import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE -import com.lagradost.cloudstream3.utils.DataStore.getFolderName +import com.lagradost.cloudstream3.utils.DataStore import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.VideoDownloadHelper @@ -99,13 +101,24 @@ class DownloadFragment : Fragment() { DownloadHeaderAdapter( ArrayList(), { click -> - if (click.data.type.isMovieType()) { - //wont be called - } else { - val folder = getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString()) - val navController = activity?.findNavController(R.id.nav_host_fragment) - navController?.navigate(R.id.navigation_download_child, DownloadChildFragment.newInstance(click.data.name,folder)) + when (click.action) { + 0 -> { + if (click.data.type.isMovieType()) { + //wont be called + } else { + val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString()) + val navController = activity?.findNavController(R.id.nav_host_fragment) + navController?.navigate( + R.id.navigation_download_child, + DownloadChildFragment.newInstance(click.data.name, folder) + ) + } + } + 1 -> { + (activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName) + } } + }, { downloadClickEvent -> if (downloadClickEvent.data !is VideoDownloadHelper.DownloadEpisodeCached) return@DownloadHeaderAdapter diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt index 720496d8..74e3cc6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt @@ -105,6 +105,9 @@ class DownloadHeaderAdapter( val d = card.data poster?.setImage(d.poster) + poster?.setOnClickListener { + clickCallback.invoke(DownloadHeaderClickEvent(1, d)) + } title.text = d.name val mbString = "%.1f".format(card.totalBytes / 1000000f) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index ac4721bf..39b2c9c7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -14,6 +14,7 @@ import android.widget.TextView import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSnapHelper @@ -166,11 +167,24 @@ class HomeFragment : Fragment() { } private val apiChangeClickListener = View.OnClickListener { view -> - val validAPIs = apis.filter { api -> api.hasMainPage }.toMutableList() + val allApis = apis.filter { api -> api.hasMainPage }.toMutableList() + var validAPIs = allApis + val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) + val currentPrefMedia = settingsManager.getInt(getString(R.string.preferred_media_settings), 0) + + // Filter API depending on preferred media type + if (currentPrefMedia > 0) { + val listEnumAnime = listOf(TvType.Anime, TvType.AnimeMovie, TvType.ONA) + val listEnumMovieTv = listOf(TvType.Movie, TvType.TvSeries, TvType.Cartoon) + val mediaTypeList = if (currentPrefMedia==1) listEnumMovieTv else listEnumAnime + + validAPIs = allApis.filter { api -> api.supportedTypes.any { it in mediaTypeList } }.toMutableList() + } + validAPIs.add(0, randomApi) validAPIs.add(0, noneApi) view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api -> Pair(index, api.name) }) { - homeViewModel.loadAndCancel(validAPIs[itemId].name) + homeViewModel.loadAndCancel(validAPIs[itemId].name, currentPrefMedia) } } @@ -411,7 +425,7 @@ class HomeFragment : Fragment() { val apiName = context?.getKey(HOMEPAGE_API) if (homeViewModel.apiName.value != apiName || apiName == null) { //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) - homeViewModel.loadAndCancel(apiName) + homeViewModel.loadAndCancel(apiName, 0) } } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index a446b1ca..5d573c41 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.HomePageResponse import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi @@ -148,12 +149,21 @@ class HomeViewModel : ViewModel() { } } - fun loadAndCancel(preferredApiName: String?) = viewModelScope.launch { + fun loadAndCancel(preferredApiName: String?, currentPrefMedia: Int) = viewModelScope.launch { val api = getApiFromNameNull(preferredApiName) if (preferredApiName == noneApi.name) loadAndCancel(noneApi) else if(preferredApiName == randomApi.name || api == null) { - loadAndCancel(apis.filter { it.hasMainPage }.random()) + val allApis = apis.filter { api -> api.hasMainPage }.toMutableList() + var validAPIs = allApis + if (currentPrefMedia > 0) { + val listEnumAnime = listOf(TvType.Anime, TvType.AnimeMovie, TvType.ONA) + val listEnumMovieTv = listOf(TvType.Movie, TvType.TvSeries, TvType.Cartoon) + val mediaTypeList = if (currentPrefMedia==1) listEnumMovieTv else listEnumAnime + + validAPIs = allApis.filter { api -> api.supportedTypes.any { it in mediaTypeList } }.toMutableList() + } + loadAndCancel(validAPIs.random()) } else { loadAndCancel(api) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt index 9e016807..89d4a228 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt @@ -32,7 +32,6 @@ import android.view.animation.AnimationUtils import android.widget.* import android.widget.Toast.LENGTH_SHORT import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider @@ -89,6 +88,7 @@ import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper.setLastWatched import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog +import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.getNavigationBarHeight import com.lagradost.cloudstream3.utils.UIHelper.getStatusBarHeight import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard @@ -829,7 +829,7 @@ class PlayerFragment : Fragment() { } isLocked = !isLocked - if(isLocked && isShowing) { + if (isLocked && isShowing) { player_pause_holder?.postDelayed({ if (isLocked && isShowing) { onClickChange() @@ -869,11 +869,13 @@ class PlayerFragment : Fragment() { private fun updateLock() { video_locked_img?.setImageResource(if (isLocked) R.drawable.video_locked else R.drawable.video_unlocked) - val color = if (isLocked) ContextCompat.getColor(requireContext(), R.color.videoColorPrimary) + val color = if (isLocked) context?.colorFromAttribute(R.attr.colorPrimary) else Color.WHITE - video_locked_text?.setTextColor(color) - video_locked_img?.setColorFilter(color) + if (color != null) { + video_locked_text?.setTextColor(color) + video_locked_img?.setColorFilter(color) + } val isClick = !isLocked @@ -996,7 +998,7 @@ class PlayerFragment : Fragment() { activity?.unregisterReceiver(it) } activity?.hideSystemUI() - this.view?.let { activity?.hideKeyboard(it) } + this.view?.let { hideKeyboard(it) } } } @@ -1938,6 +1940,9 @@ class PlayerFragment : Fragment() { ) + currentUrl.headers // Adds the headers from the provider, e.g Authorization setDefaultRequestProperties(headers) } + + //https://stackoverflow.com/questions/69040127/error-code-io-bad-http-status-exoplayer-android + setAllowCrossProtocolRedirects(true) } } else { DefaultDataSourceFactory(requireContext(), USER_AGENT) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 8752a17c..382ceb01 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -18,7 +18,6 @@ import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.text.color import androidx.core.view.isVisible @@ -196,7 +195,7 @@ class ResultFragment : Fragment() { super.onDestroy() activity?.let { it.window?.navigationBarColor = - it.colorFromAttribute(R.attr.darkBackground) + it.colorFromAttribute(R.attr.primaryGrayBackground) } } @@ -236,7 +235,7 @@ class ResultFragment : Fragment() { private var currentId: Int? = null private var currentIsMovie: Boolean? = null private var episodeRanges: List? = null - + private var dubRange: Set? = null var url: String? = null private fun fromIndexToSeasonText(selection: Int?): String { @@ -682,7 +681,6 @@ class ResultFragment : Fragment() { } outputFile.writeText(text) - val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT) vlcIntent.setPackage(VLC_PACKAGE) @@ -859,6 +857,25 @@ class ResultFragment : Fragment() { } } + observe(viewModel.dubStatus) { status -> + result_dub_select?.text = status.toString() + } + + observe(viewModel.dubSubSelections) { range -> + dubRange = range + result_dub_select?.visibility = if (range.size <= 1) GONE else VISIBLE + } + + result_dub_select.setOnClickListener { + val ranges = dubRange + if (ranges != null) { + it.popupMenuNoIconsAndNoStringRes(ranges.map { status -> Pair(status.ordinal, status.toString()) } + .toList()) { + viewModel.changeDubStatus(requireContext(), DubStatus.values()[itemId]) + } + } + } + observe(viewModel.selectedRange) { range -> result_episode_select?.text = range } @@ -959,20 +976,25 @@ class ResultFragment : Fragment() { metadataInfoArray.add(Pair(R.string.site, d.apiName)) - if (metadataInfoArray.size > 0) { - result_metadata.visibility = VISIBLE - val text = SpannableStringBuilder() - val grayColor = ContextCompat.getColor(requireContext(), R.color.grayTextColor) - val textColor = ContextCompat.getColor(requireContext(), R.color.textColor) - for (meta in metadataInfoArray) { - text.color(grayColor) { append(getString(meta.first) + ": ") } - .color(textColor) { append("${meta.second}\n") } + context?.let { ctx -> + if (metadataInfoArray.size > 0) { + result_metadata.visibility = VISIBLE + val text = SpannableStringBuilder() + val grayColor = + ctx.colorFromAttribute(R.attr.grayTextColor) //ContextCompat.getColor(requireContext(), R.color.grayTextColor) + val textColor = + ctx.colorFromAttribute(R.attr.textColor) //ContextCompat.getColor(requireContext(), R.color.textColor) + for (meta in metadataInfoArray) { + text.color(grayColor) { append(getString(meta.first) + ": ") } + .color(textColor) { append("${meta.second}\n") } + } + result_metadata.text = text + } else { + result_metadata.visibility = GONE } - result_metadata.text = text - } else { - result_metadata.visibility = GONE } + result_poster?.setImage(d.posterUrl) result_poster_holder?.visibility = if (d.posterUrl.isNullOrBlank()) GONE else VISIBLE diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index ad7f7d85..c6a09143 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -55,13 +55,20 @@ class ResultViewModel : ViewModel() { val publicEpisodes: LiveData>> get() = _publicEpisodes val publicEpisodesCount: LiveData get() = _publicEpisodesCount - private val dubStatus: MutableLiveData = MutableLiveData() + val dubStatus: MutableLiveData get() = _dubStatus + private val _dubStatus: MutableLiveData = MutableLiveData() private val page: MutableLiveData = MutableLiveData() val id: MutableLiveData = MutableLiveData() val selectedSeason: MutableLiveData = MutableLiveData(-2) val seasonSelections: MutableLiveData> = MutableLiveData() + val dubSubSelections: MutableLiveData> get() = _dubSubSelections + private val _dubSubSelections: MutableLiveData> = MutableLiveData() + + val dubSubEpisodes: MutableLiveData>?> get() = _dubSubEpisodes + private val _dubSubEpisodes: MutableLiveData>?> = MutableLiveData() + private val _watchStatus: MutableLiveData = MutableLiveData() val watchStatus: LiveData get() = _watchStatus @@ -110,7 +117,7 @@ class ResultViewModel : ViewModel() { val seasons = seasonTypes.toList().map { it.first }.sortedBy { it } seasonSelections.postValue(seasons) if (seasons.isEmpty()) { // WHAT THE FUCK DID YOU DO????? HOW DID YOU DO THIS - _publicEpisodes.postValue(Resource.Success( ArrayList())) + _publicEpisodes.postValue(Resource.Success(ArrayList())) return } @@ -160,7 +167,7 @@ class ResultViewModel : ViewModel() { selectedRange.postValue(allRange) } - _publicEpisodes.postValue(Resource.Success( currentList)) + _publicEpisodes.postValue(Resource.Success(currentList)) } fun changeSeason(context: Context, selection: Int?) { @@ -171,6 +178,13 @@ class ResultViewModel : ViewModel() { filterEpisodes(context, _episodes.value, null, range) } + fun changeDubStatus(context: Context, status: DubStatus?) { + dubSubEpisodes.value?.get(status)?.let { episodes -> + dubStatus.postValue(status) + updateEpisodes(context, null, episodes, null) + } + } + private fun updateEpisodes(context: Context, localId: Int?, list: List, selection: Int?) { _episodes.postValue(list) val set = HashMap() @@ -237,7 +251,7 @@ class ResultViewModel : ViewModel() { return name } - fun load(context: Context, url: String, apiName: String, showFillers : Boolean) = viewModelScope.launch { + fun load(context: Context, url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch { _resultResponse.postValue(Resource.Loading(url)) _publicEpisodes.postValue(Resource.Loading()) @@ -273,16 +287,21 @@ class ResultViewModel : ViewModel() { when (d) { is AnimeLoadResponse -> { - val isDub = d.dubEpisodes != null && d.dubEpisodes.isNotEmpty() - dubStatus.postValue(if (isDub) DubStatus.Dubbed else DubStatus.Subbed) + //TODO context.getKey<>() isdub - val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes) + val isDub = + d.episodes.containsKey(DubStatus.Dubbed) && !d.episodes[DubStatus.Dubbed].isNullOrEmpty() + val dubStatus = if (isDub) DubStatus.Dubbed else DubStatus.Subbed + _dubStatus.postValue(dubStatus) - val fillerEpisodes = if(showFillers) safeApiCall { getFillerEpisodes(d.name) } else null + _dubSubSelections.postValue(d.episodes.keys) + val fillerEpisodes = if (showFillers) safeApiCall { getFillerEpisodes(d.name) } else null - if (dataList != null) { // TODO dub and sub at the same time + var idIndex = 0 + val res = d.episodes.map { ep -> val episodes = ArrayList() - for ((index, i) in dataList.withIndex()) { + for ((index, i) in ep.value.withIndex()) { + val episode = i.episode ?: (index + 1) episodes.add( context.buildResultEpisode( @@ -292,17 +311,23 @@ class ResultViewModel : ViewModel() { null, // TODO FIX SEASON i.url, apiName, - (mainId + index + 1), + mainId + index + 1 + idIndex * 100000, index, i.rating, - i.descript, + i.description, if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let { it.contains(episode) && it[episode] == true - } - ?: false else false, + } ?: false else false, ) ) } + idIndex++ + + Pair(ep.key, episodes) + }.toMap() + + _dubSubEpisodes.postValue(res) + res[dubStatus]?.let { episodes -> updateEpisodes(context, mainId, episodes, -1) } } @@ -366,11 +391,10 @@ class ResultViewModel : ViewModel() { ), -1 ) } - } } else -> { - + // nothing } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 17a9fd79..caa9d78f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -35,9 +35,11 @@ import com.lagradost.cloudstream3.utils.SEARCH_PROVIDER_TOGGLE import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.getGridIsCompact import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard +import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod import kotlinx.android.synthetic.main.fragment_search.* import java.util.concurrent.locks.ReentrantLock + class SearchFragment : Fragment() { companion object { fun List.filterSearchResponse(): List { @@ -298,6 +300,10 @@ class SearchFragment : Fragment() { main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { searchViewModel.searchAndCancel(query) + main_search?.let { + hideKeyboard(it) + } + return true } @@ -360,18 +366,13 @@ class SearchFragment : Fragment() { typesActive = it.getApiTypeSettings() } - /*main_search.setOnQueryTextFocusChangeListener { searchView, b -> + main_search.setOnQueryTextFocusChangeListener { searchView, b -> if (b) { // https://stackoverflow.com/questions/12022715/unable-to-show-keyboard-automatically-in-the-searchview - searchView?.postDelayed({ - (activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager?)?.showSoftInput( - view.findFocus(), - 0 - ) - }, 200) + showInputMethod(view.findFocus()) } } - main_search.onActionViewExpanded()*/ + //main_search.onActionViewExpanded()*/ val masterAdapter: RecyclerView.Adapter = ParentItemAdapter(listOf(), { callback -> SearchHelper.handleSearchClickCallback(activity, callback) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 708873f2..6deaaaa8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -1,7 +1,11 @@ package com.lagradost.cloudstream3.ui.settings + import android.content.Intent import android.net.Uri +import android.app.UiModeManager +import android.content.Context +import android.content.res.Configuration import android.os.Bundle import android.os.Environment import android.widget.Toast @@ -40,6 +44,22 @@ import kotlin.concurrent.thread class SettingsFragment : PreferenceFragmentCompat() { + companion object { + fun Context.isTvSettings(): Boolean { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + var value = settingsManager.getInt(this.getString(R.string.app_layout_key), -1) + if (value == -1) { + value = if (isAutoTv()) 1 else 0 + } + return value == 1 + } + + private fun Context.isAutoTv(): Boolean { + val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager? + return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION + } + } + private var beneneCount = 0 // Open file picker @@ -83,6 +103,7 @@ class SettingsFragment : PreferenceFragmentCompat() { Triple("\uD83C\uDDF3\uD83C\uDDF4", "Norsk", "no"), Triple("\ud83c\udde9\ud83c\uddea", "German", "de"), Triple("🇱🇧", "Arabic", "ar"), + Triple("🇹🇷", "Turkish", "tr") ).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -97,6 +118,10 @@ class SettingsFragment : PreferenceFragmentCompat() { val subdubPreference = findPreference(getString(R.string.display_sub_key))!! val providerLangPreference = findPreference(getString(R.string.provider_lang_key))!! val downloadPathPreference = findPreference(getString(R.string.download_path_key))!! + val allLayoutPreference = findPreference(getString(R.string.app_layout_key))!! + val colorPrimaryPreference = findPreference(getString(R.string.primary_color_key))!! + val preferedMediaTypePreference = findPreference(getString(R.string.prefer_media_type_key))!! + val appThemePreference = findPreference(getString(R.string.app_theme_key))!! legalPreference.setOnPreferenceClickListener { val builder: AlertDialog.Builder = AlertDialog.Builder(it.context) @@ -177,6 +202,7 @@ class SettingsFragment : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } + fun getDownloadDirs(): List { val defaultDir = getDownloadDir()?.filePath @@ -214,6 +240,93 @@ class SettingsFragment : PreferenceFragmentCompat() { // pref = visual path settingsManager.edit().putString(getString(R.string.download_path_key), dirs[it]).apply() settingsManager.edit().putString(getString(R.string.download_path_pref), dirs[it]).apply() + + if (preferedMediaTypePreference != null) { + preferedMediaTypePreference.setOnPreferenceClickListener { + val prefNames = resources.getStringArray(R.array.media_type_pref) + val prefValues = resources.getIntArray(R.array.media_type_pref_values) + val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) + + val currentPrefMedia = + settingsManager.getInt(getString(R.string.preferred_media_settings), 0) + + context?.showBottomDialog( + prefNames.toList(), + prefValues.indexOf(currentPrefMedia), + getString(R.string.preferred_media_settings), + true, + {}) { + settingsManager.edit() + .putInt(getString(R.string.preferred_media_settings), prefValues[it]) + .apply() + context?.initRequestClient() + } + return@setOnPreferenceClickListener true + } + } + + allLayoutPreference.setOnPreferenceClickListener { + val prefNames = resources.getStringArray(R.array.app_layout) + val prefValues = resources.getIntArray(R.array.app_layout_values) + val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) + + val currentLayout = + settingsManager.getInt(getString(R.string.app_layout_key), -1) + context?.showBottomDialog( + prefNames.toList(), + prefValues.indexOf(currentLayout), + getString(R.string.app_layout), + true, + {}) { + try { + settingsManager.edit().putInt(getString(R.string.app_layout_key), prefValues[it]).apply() + activity?.recreate() + } catch (e : Exception) { + logError(e) + } + } + return@setOnPreferenceClickListener true + } + + colorPrimaryPreference.setOnPreferenceClickListener { + val prefNames = resources.getStringArray(R.array.themes_overlay_names) + val prefValues = resources.getStringArray(R.array.themes_overlay_names_values) + val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) + + val currentLayout = settingsManager.getString( getString(R.string.primary_color_key),prefValues.first()) + context?.showBottomDialog( + prefNames.toList(), + prefValues.indexOf(currentLayout), + getString(R.string.primary_color_settings), + true, + {}) { + try { + settingsManager.edit().putString(getString(R.string.primary_color_key), prefValues[it]).apply() + activity?.recreate() + } catch (e : Exception) { + logError(e) + } + } + return@setOnPreferenceClickListener true + } + + appThemePreference.setOnPreferenceClickListener { + val prefNames = resources.getStringArray(R.array.themes_names) + val prefValues = resources.getStringArray(R.array.themes_names_values) + val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) + + val currentLayout = settingsManager.getString( getString(R.string.app_theme_key),prefValues.first()) + context?.showBottomDialog( + prefNames.toList(), + prefValues.indexOf(currentLayout), + getString(R.string.app_theme_settings), + true, + {}) { + try { + settingsManager.edit().putString(getString(R.string.app_theme_key), prefValues[it]).apply() + activity?.recreate() + } catch (e : Exception) { + logError(e) } } return@setOnPreferenceClickListener true @@ -271,7 +384,7 @@ class SettingsFragment : PreferenceFragmentCompat() { settingsManager.edit().putInt(getString(R.string.benene_count), beneneCount).apply() it.summary = getString(R.string.benene_count_text).format(beneneCount) } catch (e: Exception) { - e.printStackTrace() + logError(e) } return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index ea6b1bec..a5d946de 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -36,6 +36,7 @@ import androidx.preference.PreferenceManager import com.bumptech.glide.load.model.GlideUrl import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import kotlin.math.roundToInt @@ -68,7 +69,7 @@ object UIHelper { activity?.window?.decorView?.clearFocus() view.let { if (it != null) { - activity?.hideKeyboard(it) + hideKeyboard(it) } } } @@ -202,6 +203,10 @@ object UIHelper { }*/ fun Context.getStatusBarHeight(): Int { + if(isTvSettings()) { + return 0 + } + var result = 0 val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") if (resourceId > 0) { @@ -285,9 +290,14 @@ object UIHelper { ) == AppOpsManager.MODE_ALLOWED } - fun Context.hideKeyboard(view: View) { - val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager - inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) + fun hideKeyboard(view: View) { + val inputMethodManager = view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager? + inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0) + } + + fun showInputMethod(view: View) { + val inputMethodManager = view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager? + inputMethodManager?.showSoftInput(view, 0) } /**id, stringRes */ diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 624e05b8..c94d9962 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -353,7 +353,7 @@ object VideoDownloadManager { val pending: PendingIntent = PendingIntent.getService( // BECAUSE episodes lying near will have the same id +1, index will give the same requested as the previous episode, *100000 fixes this - context, (4337 + index * 100000 + ep.id), + context, (4337 + index * 1000000 + ep.id), actionResultIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) diff --git a/app/src/main/res/color/item_select_color.xml b/app/src/main/res/color/item_select_color.xml new file mode 100644 index 00000000..0d2834dd --- /dev/null +++ b/app/src/main/res/color/item_select_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_banner_foreground.xml b/app/src/main/res/drawable/ic_banner_foreground.xml new file mode 100644 index 00000000..9586f708 --- /dev/null +++ b/app/src/main/res/drawable/ic_banner_foreground.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_bug_report_24.xml b/app/src/main/res/drawable/ic_baseline_bug_report_24.xml index c72d5c25..dad38dca 100644 --- a/app/src/main/res/drawable/ic_baseline_bug_report_24.xml +++ b/app/src/main/res/drawable/ic_baseline_bug_report_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_color_lens_24.xml b/app/src/main/res/drawable/ic_baseline_color_lens_24.xml new file mode 100644 index 00000000..3e6a50ae --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_color_lens_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_developer_mode_24.xml b/app/src/main/res/drawable/ic_baseline_developer_mode_24.xml index 55febeee..a01f1048 100644 --- a/app/src/main/res/drawable/ic_baseline_developer_mode_24.xml +++ b/app/src/main/res/drawable/ic_baseline_developer_mode_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_dns_24.xml b/app/src/main/res/drawable/ic_baseline_dns_24.xml index b2677f76..e0fa55dc 100644 --- a/app/src/main/res/drawable/ic_baseline_dns_24.xml +++ b/app/src/main/res/drawable/ic_baseline_dns_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/white"> + android:tint="?attr/white"> diff --git a/app/src/main/res/drawable/ic_baseline_hd_24.xml b/app/src/main/res/drawable/ic_baseline_hd_24.xml index 02a314e9..5cfd9a65 100644 --- a/app/src/main/res/drawable/ic_baseline_hd_24.xml +++ b/app/src/main/res/drawable/ic_baseline_hd_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml index e011dbcd..fc07eaae 100644 --- a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml @@ -1,5 +1,5 @@ diff --git a/app/src/main/res/drawable/ic_baseline_language_24.xml b/app/src/main/res/drawable/ic_baseline_language_24.xml index 0335ca0b..1749952e 100644 --- a/app/src/main/res/drawable/ic_baseline_language_24.xml +++ b/app/src/main/res/drawable/ic_baseline_language_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_notifications_active_24.xml b/app/src/main/res/drawable/ic_baseline_notifications_active_24.xml index 053e07ae..2003bfe7 100644 --- a/app/src/main/res/drawable/ic_baseline_notifications_active_24.xml +++ b/app/src/main/res/drawable/ic_baseline_notifications_active_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_ondemand_video_24.xml b/app/src/main/res/drawable/ic_baseline_ondemand_video_24.xml index bbd6d57b..80a75576 100644 --- a/app/src/main/res/drawable/ic_baseline_ondemand_video_24.xml +++ b/app/src/main/res/drawable/ic_baseline_ondemand_video_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml b/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml index 941e803b..9b623071 100644 --- a/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml +++ b/app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_skip_next_24.xml b/app/src/main/res/drawable/ic_baseline_skip_next_24.xml index 46804207..dca5cac8 100644 --- a/app/src/main/res/drawable/ic_baseline_skip_next_24.xml +++ b/app/src/main/res/drawable/ic_baseline_skip_next_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_system_update_24.xml b/app/src/main/res/drawable/ic_baseline_system_update_24.xml index 7ad99c9a..343016ee 100644 --- a/app/src/main/res/drawable/ic_baseline_system_update_24.xml +++ b/app/src/main/res/drawable/ic_baseline_system_update_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_touch_app_24.xml b/app/src/main/res/drawable/ic_baseline_touch_app_24.xml index 3a060f63..74c54fb9 100644 --- a/app/src/main/res/drawable/ic_baseline_touch_app_24.xml +++ b/app/src/main/res/drawable/ic_baseline_touch_app_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_tv_24.xml b/app/src/main/res/drawable/ic_baseline_tv_24.xml new file mode 100644 index 00000000..7194bf98 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_tv_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_warning_24.xml b/app/src/main/res/drawable/ic_baseline_warning_24.xml index 3c9a4b35..f350c7a2 100644 --- a/app/src/main/res/drawable/ic_baseline_warning_24.xml +++ b/app/src/main/res/drawable/ic_baseline_warning_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_outline_subtitles_24.xml b/app/src/main/res/drawable/ic_outline_subtitles_24.xml index 3b0780bf..7d692c67 100644 --- a/app/src/main/res/drawable/ic_outline_subtitles_24.xml +++ b/app/src/main/res/drawable/ic_outline_subtitles_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_outline_voice_over_off_24.xml b/app/src/main/res/drawable/ic_outline_voice_over_off_24.xml index b6ed06ac..a77dfd72 100644 --- a/app/src/main/res/drawable/ic_outline_voice_over_off_24.xml +++ b/app/src/main/res/drawable/ic_outline_voice_over_off_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/outline_card.xml b/app/src/main/res/drawable/outline_card.xml index 442c662e..02116bb8 100644 --- a/app/src/main/res/drawable/outline_card.xml +++ b/app/src/main/res/drawable/outline_card.xml @@ -14,7 +14,7 @@ - + diff --git a/app/src/main/res/drawable/tab_selector.xml b/app/src/main/res/drawable/tab_selector.xml index ab31dbf8..92bb27dd 100644 --- a/app/src/main/res/drawable/tab_selector.xml +++ b/app/src/main/res/drawable/tab_selector.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/video_bottom_button.xml b/app/src/main/res/drawable/video_bottom_button.xml new file mode 100644 index 00000000..ee4f97df --- /dev/null +++ b/app/src/main/res/drawable/video_bottom_button.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 7f949ab5..ae179034 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,7 +6,7 @@ android:id="@+id/homeRoot" android:layout_width="match_parent" android:layout_height="match_parent" - android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" + android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard|navigation" android:paddingTop="0dp"> + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_loading.xml b/app/src/main/res/layout/dialog_loading.xml index cb1ee5ac..69fe443d 100644 --- a/app/src/main/res/layout/dialog_loading.xml +++ b/app/src/main/res/layout/dialog_loading.xml @@ -1,18 +1,23 @@ - + android:layout_height="match_parent"> + - + @@ -71,7 +71,7 @@ android:layout_gravity="center_vertical" android:gravity="center_vertical" tools:text="128MB / 237MB" - android:textColor="@color/grayTextColor" + android:textColor="?attr/grayTextColor" android:layout_width="wrap_content" android:layout_height="match_parent"> @@ -111,6 +111,7 @@ android:layout_width="30dp" android:background="?selectableItemBackgroundBorderless" android:src="@drawable/ic_baseline_play_arrow_24" + android:tint="?attr/textColor" android:contentDescription="@string/download"/> diff --git a/app/src/main/res/layout/download_header_episode.xml b/app/src/main/res/layout/download_header_episode.xml index ff4c62cf..31ccabd8 100644 --- a/app/src/main/res/layout/download_header_episode.xml +++ b/app/src/main/res/layout/download_header_episode.xml @@ -6,7 +6,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" app:cardCornerRadius="@dimen/roundedImageRadius" - app:cardBackgroundColor="@color/itemBackground" + app:cardBackgroundColor="?attr/boxItemBackground" android:id="@+id/episode_holder" android:foreground="@drawable/outline_drawable" android:layout_marginBottom="10dp" @@ -41,14 +41,14 @@ android:id="@+id/download_header_title" tools:text="Perfect Run" android:textStyle="bold" - android:textColor="@color/textColor" + android:textColor="?attr/textColor" android:layout_width="wrap_content" android:layout_height="wrap_content"> @@ -85,6 +85,8 @@ android:visibility="visible" /> - search_providers_list - app_locale - search_type_list - grid_format - auto_update - prerelease_update - manual_check_update - fast_forward_button_time - benene_count - subtitle_settings_key - quality_pref_key - unknown_prerelease - use_system_brightness_key - swipe_enabled_key - playback_speed_enabled_key - player_resize_enabled_key - pip_enabled_key - double_tap_enabled_key - swipe_vertical_enabled_key - display_sub_key - show_fillers_key - provider_lang_key - dns_key - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %s %s - +%d - -%d - %d - %d %s Ep %d @@ -43,9 +12,7 @@ Episodyo Paskil Pangunahing Paskil Susunod Walang tiyak - @string/play_episode Pumunta Likod - @string/home_change_provider_img_des Pagbabago Pinagmulan Prebiyu Likuran @@ -65,7 +32,6 @@ Wala Datos Mas Opsyon Susunod Episodyo - @string/synopsis Dyanra Magbahagi Buksan Ito Panlabas @@ -96,7 +62,6 @@ Pag-iimbak nabigo Pag-iimbak Kinansela Tapos - %s - %s Kamalian Espasyo sa Imbakan @@ -204,7 +169,6 @@ Tanggalin Tanggalin - @string/sort_cancel Humawak Magpatuloy Permanenteng tatanggalin %s\nSigurado ka ba? @@ -262,31 +226,19 @@ DNS over HTTPS Mahusay gamitin sa pag-bypass ISP sagabal - Magpakita Dubbed/Subbed Anime Pinakamainam Palakihin Magpalaki - Disclaimer - legal_notice_key - Any legal issues regarding the content on this application - should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. - - In case of copyright infringement, please directly contact the responsible parties or the streaming websites. - - The app is purely for educational and personal use. - - CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. - CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or - manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, - user-friendly interface. - - It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the - responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use - CloudStream 3 at your own risk. - - General + Pangkalahatan Pinagmulan wika - \ No newline at end of file + App Ayos + Automatiko + Telebisyon Ayos + Phone Ayos + Pangunahin Kulay + App Tema + Ginustong Nilalaman + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml new file mode 100644 index 00000000..a5315702 --- /dev/null +++ b/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,280 @@ + + + + search_providers_list + app_locale + search_type_list + grid_format + auto_update + prerelease_update + manual_check_update + fast_forward_button_time + benene_count + subtitle_settings_key + quality_pref_key + unknown_prerelease + use_system_brightness_key + swipe_enabled_key + playback_speed_enabled_key + player_resize_enabled_key + pip_enabled_key + double_tap_enabled_key + swipe_vertical_enabled_key + display_sub_key + show_fillers_key + provider_lang_key + dns_key + app_layout_key + primary_color_key + + + %d %s | %sMB + %s • %sGB + %sMB / %sMB + %s %s + +%d + -%d + %d + %d + %s Ep %d + + + Poster + @string/result_poster_img_des + Episode Poster + Main Poster + Next Random + @string/play_episode + Go back + @string/home_change_provider_img_des + Change Provider + Preview Background + + + Hız (%.2fx) + Puan: %.1f + Yeni güncelleme bulundu!\n%s -> %s + (Doldurucu) %s + + CloudStream + Ana Sayfa + Arama + İndirmeler + Ayarlar + + Ara… + Veri Yok + Daha Fazla Seçenek + Sonraki bölüm + Türler + Paylaş + Tarayıcıda Aç + Yüklemeyi geç + Yükleniyor… + + İzleniyor + Beklemede + Tamamlandı + Bırakıldı + İzlemeyi Planla + Hiçbiri + + Film Oynat + Torrent Akışı + Kaynaklar + Altyazılar + Yeniden bağlanmayı dene… + Geri Git + Bölümü Oynat + + + İndirme + İndirildi + İndiriliyor + İndirme Durduruldu + İndirme Başladı + İndirme Başarısız Oldu + İndirme İptal Edildi + İndirme Bitti + + Bağlantıları yüklerken hata + Dahili Depolama + + Seslendirme + Altyazı + + Dosyayı Sil + Dosyayı Oynat + İndirmeyi Sürdür + İndirmeyi Durdur + + Otomatik hata bildirmeyi devre dışı bırak + Daha fazka bilgi + Gizle + Oynat + Bilgi + Yer işaretlerini filtrele + Yer işaretleri + Kaldır + Uygula + İptal Et + Oynatıcı Hızı + + Altyazı Ayarları + Metin Rengi + Anahat Rengi + Arkaplan Rengi + Pencere Rengi + Kenar Tipi + Altyazı Yüksekliği + Yazı Tipi + Yazı Tipi Boyutu + + Sağlayıcıları kullanarak ara + Türleri kullanarak ara + + Geliştiricilere %d Benene verildi + Hiçbir Benene verilmedi + + Dili Otomatik Seç + Dilleri İndir + Varsayılana dönmek için basılı tutun + İzlemeye Devam Edin + + Kaldır + Daha Fazla Bilgi + + Bu sağlayıcının düzgün bir şekilde çalışması için VPN gerekebilir + Bu sağlayıcınınki Torrent, VPN önerilir + Açıklama + Olay örgüsü bulunamadı + Açıklama bulunamadı + + Resim içinde resim + Diğer uygulamaların üzerinde minyatür bir oynatıcıda oynatmaya devam eder + Oyuncu yeniden boyutlandırma düğmesi + Siyah kenarlıkları kaldır + Altyazılar + Oynatıcı altyazı ayarları + Eigengravy Modu + Oynatıcıya hız seçeneğini ekler + Aramak için kaydırın + Video oynatıcıda zamanı kontrol etmek için sola veya sağa kaydırın + Ayarları değiştirmek için kaydırın + Parlaklığı veya ses seviyesini değiştirmek için sola veya sağa kaydırın + Aramak için iki kez dokunun + İleri veya geri aramak için sağ veya sol tarafa iki kez dokunun + + Sistem parlaklığını kullan + Uygulama oynatıcısında karanlık bir kaplama yerine sistem parlaklığını kullanın + + Arama + Bilgi + Gelişmiş Arama + Sağlayıcı tarafından ayrılmış arama sonuçlarını verir + Yalnızca çökmelerle ilgili verileri gönderir + Hiçbir veri göndermez + Anime için doldurucu bölümü göster + Uygulama güncellemelerini göster + Başlangıçta yeni güncellemeleri otomatik olarak ara + Ön sürümlere güncelleme + Yalnızca tam sürümler yerine önsürüm güncellemeleri arayın + Github + Aynı geliştiricilerden Light novel uygulaması + Aynı geliştiricilerden Anime uygulaması + Discord\'a gelin + Geliştiricilere muz ver + Verilen benene + + Uygulama Dili + + Bu sağlayıcının chromecast desteği yok + Hiçbir Bağlantı Bulumamadı + Bağlantı panoya kopyalandı + Bölümü Oynat + Varsayılan değere sıfırla + Üzgünüm, uygulama çöktü. Anonim bir hata raporu geliştiricilere gönderilecek + + Sezon + Sezon Yok + Bölüm + Bölümler + S + B + + Dosyayı Sil + Sil + Durdur + Sürdür + %s kalıcı olarak silinecektir. Emin misiniz? + + Devam eden + Tamamlandı + Durum + Yıl + Derecelendirme + Süre + Site + Özet + + sıraya eklendi + Altyazı yok + Varsayılan + + Bağımsız + Kullanıldı + Uygulama + + Filmler + Televizyon Dizisi + Karikatürler + Anime + Torrent + + Kaynak hatası + Uzaktan hata + İşleyici hatası + Beklenmeyen oynatıcı hatası + İndirme hatası, depolama izinlerini kontrol et + + Chromecast Bölümü + Chromecast Yansıtma + Uygulamada Oynat + VLC\'de Oynat + Tarayıcıda Oynat + Bağlantıyı Kopyala + Otomatik İndirme + İndirme Yansıması + Bağlantıları yeniden yükle + + Güncelleme bulunamadı + Güncellemeleri kontrol et + + Kilitle + Yeniden boyutlandır + Kaynak + OP\'u geç + + Bir daha gösterme + Güncelleme + Tercih edilen izleme kalitesi + HTTPS üzerinden DNS + ISS\'in engellemelerini geçmek için kullanışlı + + + Seslendirilmiş/Altyazılı Anime\'yi göster + + Ekrana sığdır + Yay + Yakınlaştır + + Genel + Sağlayıcı Dilleri + Uygulama Düzeni + + Otomatik + Tv Düzeni + Telefon Düzeni + + Ana Renk + diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 88d4247e..3be81832 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -9,7 +9,6 @@ Thật tiếc! Không có kết quả nào phù hợp. Hãy thử nhập một tên khác More Options Tập tiếp theo - @string/synopsis Thể loại Chia sẻ Mở bằng trình duyệt diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index c2a1504f..bff99d48 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -46,6 +46,17 @@ 4 + + All + Movies and TV + Anime + + + 0 + 1 + 2 + + @string/episode_action_chomecast_episode @string/episode_action_chomecast_mirror @@ -68,4 +79,48 @@ 7 8 + + + @string/automatic + @string/phone_layout + @string/tv_layout + + + + -1 + 0 + 1 + + + + Normal + Cool + Fire + Burple + Green + Apple + Banana + Party + + + Normal + Blue + Red + Purple + Green + GreenApple + Banana + Party + + + + Normal + Amoled + Flashbang + + + Black + Amoled + Light + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index c99f9022..5dacd9c1 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -16,9 +16,8 @@ - - + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b5ec6864..ebb4ed7b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,7 +3,6 @@ #3d50fa @color/colorPrimary #303135 - #1E1E32 #F53B66 #3700B3 #3b65f5 @@ -11,16 +10,17 @@ #2B2C30 #111111 #1C1C20 - #161616 + #161616 #e9eaee #9ba0a4 - @color/textColor #1AFFFFFF #00000000 #FFF + #000 + #3d50fa #803B65F5 #F54A3B @@ -34,10 +34,27 @@ #121212 #66B5B5B5 - @color/colorPrimary #9ba0a6 - #FFF - #676767 + + + + + #f1f1f1 + #fff + #eeeeee + #eeeeee + #202125 + #5f6267 + #5f6267 + + + #5664B7 + #D50000 + #6200EA + #00BFA5 + #48E484 + #E4D448 + #ea596e \ No newline at end of file diff --git a/app/src/main/res/values/ic_banner_background.xml b/app/src/main/res/values/ic_banner_background.xml new file mode 100644 index 00000000..8661bdd1 --- /dev/null +++ b/app/src/main/res/values/ic_banner_background.xml @@ -0,0 +1,4 @@ + + + #3242D3 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a56e8b71..10fd2b62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,9 +25,11 @@ provider_lang_key dns_key download_path_key - - Cloudstream + app_layout_key + primary_color_key + prefer_media_type_key + app_theme_key @@ -279,7 +281,7 @@ Stretch Zoom - Disclaimer + Disclaimer legal_notice_key Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. @@ -299,4 +301,13 @@ General Provider Languages + App Layout + Preferred Media + + Auto + Tv Layout + Phone Layout + + Primary Color + App Theme diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 7e577030..da49da7c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,9 +8,6 @@ ?attr/iconGrayBackground - @color/searchColor - @color/textColor - @null @null @@ -32,22 +29,139 @@ @style/CustomCastMiniController + + ?attr/textColor + ?attr/grayTextColor + ?attr/grayTextColor + + ?attr/grayTextColor + ?attr/textColor + ?attr/textColor + ?attr/textColor + + true + true + + @color/colorPrimary @color/colorPrimaryDark @color/colorAccent @color/textColor - @color/colorItemSeen @color/grayTextColor - @color/primaryGrayBackground + @color/primaryGrayBackground @color/primaryBlackBackground @color/iconGrayBackground - @color/itemBackground + @color/boxItemBackground @color/iconColor #FFF + + + + + + + + + + + + + + + + + + + + + + +