Merge branch 'recloudstream:master' into master

This commit is contained in:
RowdyRushya 2024-05-01 19:31:10 -07:00 committed by GitHub
commit ef7b66989c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 267 additions and 20 deletions

View file

@ -0,0 +1,101 @@
package com.lagradost.cloudstream3.extractors
import android.util.Log
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.Qualities
import org.mozilla.javascript.Context
import org.mozilla.javascript.NativeJSON
import org.mozilla.javascript.NativeObject
import org.mozilla.javascript.Scriptable
import java.util.Base64
open class Vidguardto : ExtractorApi() {
override val name = "Vidguard"
override val mainUrl = "https://vidguard.to"
override val requiresReferer = false
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val res = app.get(url)
val resc = res.document.select("script:containsData(eval)").firstOrNull()?.data()
resc?.let {
val jsonStr2 = AppUtils.parseJson<SvgObject>(runJS2(it))
val watchlink = sigDecode(jsonStr2.stream)
callback.invoke(
ExtractorLink(
this.name,
name,
watchlink,
this.mainUrl,
Qualities.Unknown.value,
INFER_TYPE
)
)
}
}
private fun sigDecode(url: String): String {
val sig = url.split("sig=")[1].split("&")[0]
var t = ""
for (v in sig.chunked(2)) {
val byteValue = Integer.parseInt(v, 16) xor 2
t += byteValue.toChar()
}
val padding = when (t.length % 4) {
2 -> "=="
3 -> "="
else -> ""
}
val decoded = Base64.getDecoder().decode((t + padding).toByteArray(Charsets.UTF_8))
t = String(decoded).dropLast(5).reversed()
val charArray = t.toCharArray()
for (i in 0 until charArray.size - 1 step 2) {
val temp = charArray[i]
charArray[i] = charArray[i + 1]
charArray[i + 1] = temp
}
val modifiedSig = String(charArray).dropLast(5)
return url.replace(sig, modifiedSig)
}
private fun runJS2(hideMyHtmlContent: String): String {
Log.d("runJS", "start")
val rhino = Context.enter()
rhino.initSafeStandardObjects()
rhino.optimizationLevel = -1
val scope: Scriptable = rhino.initSafeStandardObjects()
scope.put("window", scope, scope)
var result = ""
try {
Log.d("runJS", "Executing JavaScript: $hideMyHtmlContent")
rhino.evaluateString(scope, hideMyHtmlContent, "JavaScript", 1, null)
val svgObject = scope.get("svg", scope)
result = if (svgObject is NativeObject) {
NativeJSON.stringify(Context.getCurrentContext(), scope, svgObject, null, null).toString()
} else {
Context.toString(svgObject)
}
Log.d("runJS", "Result: $result")
} catch (e: Exception) {
Log.e("runJS", "Error executing JavaScript", e)
} finally {
Context.exit()
}
return result
}
data class SvgObject(
val stream: String,
val hash: String
)
}

View file

@ -1,13 +1,37 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import android.util.Base64
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
class Tubeless : Voe() { class Tubeless : Voe() {
override var mainUrl = "https://tubelessceliolymph.com" override val name = "Tubeless"
override val mainUrl = "https://tubelessceliolymph.com"
}
class Simpulumlamerop : Voe() {
override val name = "Simplum"
override var mainUrl = "https://simpulumlamerop.com"
}
class Urochsunloath : Voe() {
override val name = "Uroch"
override var mainUrl = "https://urochsunloath.com"
}
class Yipsu : Voe() {
override val name = "Yipsu"
override var mainUrl = "https://yip.su"
}
class MetaGnathTuggers : Voe() {
override val name = "Metagnath"
override val mainUrl = "https://metagnathtuggers.com"
} }
open class Voe : ExtractorApi() { open class Voe : ExtractorApi() {
@ -15,6 +39,9 @@ open class Voe : ExtractorApi() {
override val mainUrl = "https://voe.sx" override val mainUrl = "https://voe.sx"
override val requiresReferer = true override val requiresReferer = true
private val linkRegex = "(http|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])".toRegex()
private val base64Regex = Regex("'.*'")
override suspend fun getUrl( override suspend fun getUrl(
url: String, url: String,
referer: String?, referer: String?,
@ -25,12 +52,33 @@ open class Voe : ExtractorApi() {
val script = res.select("script").find { it.data().contains("sources =") }?.data() val script = res.select("script").find { it.data().contains("sources =") }?.data()
val link = Regex("[\"']hls[\"']:\\s*[\"'](.*)[\"']").find(script ?: return)?.groupValues?.get(1) val link = Regex("[\"']hls[\"']:\\s*[\"'](.*)[\"']").find(script ?: return)?.groupValues?.get(1)
M3u8Helper.generateM3u8( val videoLinks = mutableListOf<String>()
name,
link ?: return,
"$mainUrl/",
headers = mapOf("Origin" to "$mainUrl/")
).forEach(callback)
if (!link.isNullOrBlank()) {
videoLinks.add(
when {
linkRegex.matches(link) -> link
else -> String(Base64.decode(link, Base64.DEFAULT))
}
)
} else {
val link2 = base64Regex.find(script)?.value ?: return
val decoded = Base64.decode(link2, Base64.DEFAULT).toString()
val videoLinkDTO = AppUtils.parseJson<WcoSources>(decoded)
videoLinkDTO.let { videoLinks.add(it.toString()) }
}
videoLinks.forEach { videoLink ->
M3u8Helper.generateM3u8(
name,
videoLink,
"$mainUrl/",
headers = mapOf("Origin" to "$mainUrl/")
).forEach(callback)
}
} }
data class WcoSources(
@JsonProperty("VideoLinkDTO") val VideoLinkDTO: String,
)
} }

View file

@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.getKeys import com.lagradost.cloudstream3.utils.DataStore.getKeys
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -89,9 +90,9 @@ class DownloadChildFragment : Fragment() {
setNavigationOnClickListener { setNavigationOnClickListener {
activity?.onBackPressedDispatcher?.onBackPressed() activity?.onBackPressedDispatcher?.onBackPressed()
} }
setAppBarNoScrollFlagsOnTV()
} }
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
DownloadChildAdapter( DownloadChildAdapter(
ArrayList(), ArrayList(),

View file

@ -41,6 +41,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import java.net.URI import java.net.URI
@ -97,6 +98,8 @@ class DownloadFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
hideKeyboard() hideKeyboard()
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
observe(downloadsViewModel.noDownloadsText) { observe(downloadsViewModel.noDownloadsText) {
binding?.textNoDownloads?.text = it binding?.textNoDownloads?.text = it
} }

View file

@ -9,9 +9,11 @@ import androidx.core.view.isVisible
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding
import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
@ -23,6 +25,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.* import java.util.*
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
@ -104,7 +108,7 @@ class EpisodeAdapter(
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
val item = getItem(position) val item = getItem(position)
return if (item.poster.isNullOrBlank()) 0 else 1 return if (item.poster.isNullOrBlank() && item.description.isNullOrBlank()) 0 else 1
} }
@ -260,6 +264,33 @@ class EpisodeAdapter(
} }
} }
if (card.airDate != null) {
val isUpcoming = unixTimeMS < card.airDate
if (isUpcoming) {
episodePlayIcon.isVisible = false
episodeUpcomingIcon.isVisible = !episodePoster.isVisible
episodeDate.setText(
txt(
R.string.episode_upcoming_format,
secondsToReadable(card.airDate.minus(unixTimeMS).div(1000).toInt(), "")
)
)
} else {
episodeUpcomingIcon.isVisible = false
val formattedAirDate = SimpleDateFormat.getDateInstance(
DateFormat.LONG,
Locale.getDefault()
).apply {
}.format(Date(card.airDate))
episodeDate.setText(txt(formattedAirDate))
}
} else {
episodeDate.isVisible = false
}
if (isLayout(EMULATOR or PHONE)) { if (isLayout(EMULATOR or PHONE)) {
episodePoster.setOnClickListener { episodePoster.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
@ -271,6 +302,7 @@ class EpisodeAdapter(
} }
} }
} }
itemView.setOnClickListener { itemView.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
} }

View file

@ -50,6 +50,7 @@ data class ResultEpisode(
val videoWatchState: VideoWatchState, val videoWatchState: VideoWatchState,
/** Sum of all previous season episode counts + episode */ /** Sum of all previous season episode counts + episode */
val totalEpisodeIndex: Int? = null, val totalEpisodeIndex: Int? = null,
val airDate: Long? = null,
) )
fun ResultEpisode.getRealPosition(): Long { fun ResultEpisode.getRealPosition(): Long {
@ -85,6 +86,7 @@ fun buildResultEpisode(
tvType: TvType, tvType: TvType,
parentId: Int, parentId: Int,
totalEpisodeIndex: Int? = null, totalEpisodeIndex: Int? = null,
airDate: Long? = null,
): ResultEpisode { ): ResultEpisode {
val posDur = getViewPos(id) val posDur = getViewPos(id)
val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None
@ -107,7 +109,8 @@ fun buildResultEpisode(
tvType, tvType,
parentId, parentId,
videoWatchState, videoWatchState,
totalEpisodeIndex totalEpisodeIndex,
airDate,
) )
} }

View file

@ -1099,13 +1099,14 @@ class ResultViewModel2 : ViewModel() {
val duplicateEntries = data.filter { it: DataStoreHelper.LibrarySearchResponse -> val duplicateEntries = data.filter { it: DataStoreHelper.LibrarySearchResponse ->
val librarySyncData = it.syncData val librarySyncData = it.syncData
val yearCheck = year == it.year || year == null || it.year == null
val checks = listOf( val checks = listOf(
{ imdbId != null && getImdbIdFromSyncData(librarySyncData) == imdbId }, { imdbId != null && getImdbIdFromSyncData(librarySyncData) == imdbId },
{ tmdbId != null && getTMDbIdFromSyncData(librarySyncData) == tmdbId }, { tmdbId != null && getTMDbIdFromSyncData(librarySyncData) == tmdbId },
{ malId != null && librarySyncData?.get(AccountManager.malApi.idPrefix) == malId }, { malId != null && librarySyncData?.get(AccountManager.malApi.idPrefix) == malId },
{ aniListId != null && librarySyncData?.get(AccountManager.aniListApi.idPrefix) == aniListId }, { aniListId != null && librarySyncData?.get(AccountManager.aniListApi.idPrefix) == aniListId },
{ normalizedName == normalizeString(it.name) && year == it.year } { normalizedName == normalizeString(it.name) && yearCheck }
) )
checks.any { it() } checks.any { it() }
@ -2277,7 +2278,8 @@ class ResultViewModel2 : ViewModel() {
fillers.getOrDefault(episode, false), fillers.getOrDefault(episode, false),
loadResponse.type, loadResponse.type,
mainId, mainId,
totalIndex totalIndex,
airDate = i.date
) )
val season = eps.seasonIndex ?: 0 val season = eps.seasonIndex ?: 0
@ -2326,7 +2328,8 @@ class ResultViewModel2 : ViewModel() {
null, null,
loadResponse.type, loadResponse.type,
mainId, mainId,
totalIndex totalIndex,
airDate = episode.date
) )
val season = ep.seasonIndex ?: 0 val season = ep.seasonIndex ?: 0

View file

@ -83,6 +83,7 @@ import com.lagradost.cloudstream3.extractors.Maxstream
import com.lagradost.cloudstream3.extractors.Mcloud import com.lagradost.cloudstream3.extractors.Mcloud
import com.lagradost.cloudstream3.extractors.Megacloud import com.lagradost.cloudstream3.extractors.Megacloud
import com.lagradost.cloudstream3.extractors.Meownime import com.lagradost.cloudstream3.extractors.Meownime
import com.lagradost.cloudstream3.extractors.MetaGnathTuggers
import com.lagradost.cloudstream3.extractors.Minoplres import com.lagradost.cloudstream3.extractors.Minoplres
import com.lagradost.cloudstream3.extractors.MixDrop import com.lagradost.cloudstream3.extractors.MixDrop
import com.lagradost.cloudstream3.extractors.MixDropBz import com.lagradost.cloudstream3.extractors.MixDropBz
@ -139,6 +140,7 @@ import com.lagradost.cloudstream3.extractors.Sbspeed
import com.lagradost.cloudstream3.extractors.Sbthe import com.lagradost.cloudstream3.extractors.Sbthe
import com.lagradost.cloudstream3.extractors.Sendvid import com.lagradost.cloudstream3.extractors.Sendvid
import com.lagradost.cloudstream3.extractors.ShaveTape import com.lagradost.cloudstream3.extractors.ShaveTape
import com.lagradost.cloudstream3.extractors.Simpulumlamerop
import com.lagradost.cloudstream3.extractors.Solidfiles import com.lagradost.cloudstream3.extractors.Solidfiles
import com.lagradost.cloudstream3.extractors.Ssbstream import com.lagradost.cloudstream3.extractors.Ssbstream
import com.lagradost.cloudstream3.extractors.StreamM4u import com.lagradost.cloudstream3.extractors.StreamM4u
@ -175,6 +177,7 @@ import com.lagradost.cloudstream3.extractors.UpstreamExtractor
import com.lagradost.cloudstream3.extractors.Uqload import com.lagradost.cloudstream3.extractors.Uqload
import com.lagradost.cloudstream3.extractors.Uqload1 import com.lagradost.cloudstream3.extractors.Uqload1
import com.lagradost.cloudstream3.extractors.Uqload2 import com.lagradost.cloudstream3.extractors.Uqload2
import com.lagradost.cloudstream3.extractors.Urochsunloath
import com.lagradost.cloudstream3.extractors.Userload import com.lagradost.cloudstream3.extractors.Userload
import com.lagradost.cloudstream3.extractors.Userscloud import com.lagradost.cloudstream3.extractors.Userscloud
import com.lagradost.cloudstream3.extractors.Uservideo import com.lagradost.cloudstream3.extractors.Uservideo
@ -187,6 +190,7 @@ import com.lagradost.cloudstream3.extractors.VideoVard
import com.lagradost.cloudstream3.extractors.VideovardSX import com.lagradost.cloudstream3.extractors.VideovardSX
import com.lagradost.cloudstream3.extractors.Vidgomunime import com.lagradost.cloudstream3.extractors.Vidgomunime
import com.lagradost.cloudstream3.extractors.Vidgomunimesb import com.lagradost.cloudstream3.extractors.Vidgomunimesb
import com.lagradost.cloudstream3.extractors.Vidguardto
import com.lagradost.cloudstream3.extractors.VidhideExtractor import com.lagradost.cloudstream3.extractors.VidhideExtractor
import com.lagradost.cloudstream3.extractors.Vidmoly import com.lagradost.cloudstream3.extractors.Vidmoly
import com.lagradost.cloudstream3.extractors.Vidmolyme import com.lagradost.cloudstream3.extractors.Vidmolyme
@ -208,6 +212,7 @@ import com.lagradost.cloudstream3.extractors.Watchx
import com.lagradost.cloudstream3.extractors.WcoStream import com.lagradost.cloudstream3.extractors.WcoStream
import com.lagradost.cloudstream3.extractors.Wibufile import com.lagradost.cloudstream3.extractors.Wibufile
import com.lagradost.cloudstream3.extractors.XStreamCdn import com.lagradost.cloudstream3.extractors.XStreamCdn
import com.lagradost.cloudstream3.extractors.Yipsu
import com.lagradost.cloudstream3.extractors.YourUpload import com.lagradost.cloudstream3.extractors.YourUpload
import com.lagradost.cloudstream3.extractors.YoutubeExtractor import com.lagradost.cloudstream3.extractors.YoutubeExtractor
import com.lagradost.cloudstream3.extractors.YoutubeMobileExtractor import com.lagradost.cloudstream3.extractors.YoutubeMobileExtractor
@ -890,7 +895,12 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
StreamWishExtractor(), StreamWishExtractor(),
EmturbovidExtractor(), EmturbovidExtractor(),
Vtbe(), Vtbe(),
EPlayExtractor() EPlayExtractor(),
Vidguardto(),
Simpulumlamerop(),
Urochsunloath(),
Yipsu(),
MetaGnathTuggers()
) )

View file

@ -45,6 +45,7 @@ import androidx.core.view.marginBottom
import androidx.core.view.marginLeft import androidx.core.view.marginLeft
import androidx.core.view.marginRight import androidx.core.view.marginRight
import androidx.core.view.marginTop import androidx.core.view.marginTop
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
@ -58,6 +59,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions.bitmapTransform import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable import com.google.android.material.chip.ChipDrawable
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
@ -208,6 +210,14 @@ object UIHelper {
} }
} }
fun View?.setAppBarNoScrollFlagsOnTV() {
if (isLayout(Globals.TV or EMULATOR)) {
this?.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
}
}
}
fun Activity.hideKeyboard() { fun Activity.hideKeyboard() {
window?.decorView?.clearFocus() window?.decorView?.clearFocus()
this.findViewById<View>(android.R.id.content)?.rootView?.let { this.findViewById<View>(android.R.id.content)?.rootView?.let {

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#9BA0A4"
android:pathData="M320,800h320v-120q0,-66 -47,-113t-113,-47q-66,0 -113,47t-47,113v120ZM480,440q66,0 113,-47t47,-113v-120L320,160v120q0,66 47,113t113,47ZM160,880v-80h80v-120q0,-61 28.5,-114.5T348,480q-51,-32 -79.5,-85.5T240,280v-120h-80v-80h640v80h-80v120q0,61 -28.5,114.5T612,480q51,32 79.5,85.5T720,680v120h80v80L160,880ZM480,800ZM480,160Z"/>
</vector>

View file

@ -9,6 +9,7 @@
android:layout_height="50dp" android:layout_height="50dp"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:foreground="@drawable/outline_drawable" android:foreground="@drawable/outline_drawable"
android:focusable="true"
android:nextFocusLeft="@id/nav_rail_view" android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/download_button" android:nextFocusRight="@id/download_button"
app:cardBackgroundColor="@color/transparent" app:cardBackgroundColor="@color/transparent"
@ -84,7 +85,9 @@
android:layout_height="@dimen/download_size" android:layout_height="@dimen/download_size"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:layout_marginStart="-50dp" android:layout_marginStart="-50dp"
android:background="?selectableItemBackgroundBorderless" android:foreground="@drawable/outline_drawable"
android:focusable="true"
android:nextFocusLeft="@id/download_child_episode_holder"
android:padding="10dp" /> android:padding="10dp" />
</GridLayout> </GridLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View file

@ -9,6 +9,8 @@
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:foreground="@drawable/outline_drawable" android:foreground="@drawable/outline_drawable"
android:focusable="true"
android:nextFocusRight="@id/download_button"
app:cardBackgroundColor="?attr/boxItemBackground" app:cardBackgroundColor="?attr/boxItemBackground"
app:cardCornerRadius="@dimen/rounded_image_radius"> app:cardCornerRadius="@dimen/rounded_image_radius">
@ -71,7 +73,9 @@
android:layout_height="@dimen/download_size" android:layout_height="@dimen/download_size"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:layout_marginStart="-50dp" android:layout_marginStart="-50dp"
android:background="?selectableItemBackgroundBorderless" android:foreground="@drawable/outline_drawable"
android:focusable="true"
android:nextFocusLeft="@id/episode_holder"
android:padding="10dp" /> android:padding="10dp" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View file

@ -43,14 +43,26 @@
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:nextFocusRight="@id/download_button" android:nextFocusRight="@id/download_button"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@drawable/example_poster" /> tools:src="@drawable/example_poster"
tools:visibility="invisible"/>
<ImageView <ImageView
android:id="@+id/episode_play_icon"
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="36dp" android:layout_height="36dp"
android:layout_gravity="center" android:layout_gravity="center"
android:contentDescription="@string/play_episode" android:contentDescription="@string/play_episode"
android:src="@drawable/play_button" /> android:src="@drawable/play_button"
tools:visibility="invisible"/>
<ImageView
android:id="@+id/episode_upcoming_icon"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:src="@drawable/hourglass_24"
android:visibility="gone"
tools:visibility="visible" />
<androidx.core.widget.ContentLoadingProgressBar <androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/episode_progress" android:id="@+id/episode_progress"
@ -100,6 +112,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?attr/grayTextColor" android:textColor="?attr/grayTextColor"
tools:text="Rated: 8.8" /> tools:text="Rated: 8.8" />
<TextView
android:id="@+id/episode_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/grayTextColor"
tools:text="15 Apr 2024" />
</LinearLayout> </LinearLayout>
<com.lagradost.cloudstream3.ui.download.button.PieFetchButton <com.lagradost.cloudstream3.ui.download.button.PieFetchButton

View file

@ -292,6 +292,7 @@
<string name="episodes">Episodes</string> <string name="episodes">Episodes</string>
<string name="episodes_range">%1$d-%2$d</string> <string name="episodes_range">%1$d-%2$d</string>
<string name="episode_format" formatted="true">%1$d %2$s</string> <string name="episode_format" formatted="true">%1$d %2$s</string>
<string name="episode_upcoming_format" formatted="true">Upcoming in %s</string>
<string name="season_short">S</string> <string name="season_short">S</string>
<string name="episode_short">E</string> <string name="episode_short">E</string>
<string name="no_episodes_found">No Episodes found</string> <string name="no_episodes_found">No Episodes found</string>